Add length, save data ID, and start time directly to the root of the replay data. Fix bug where profile index increase was being reset.
238 lines
7.9 KiB
C++
238 lines
7.9 KiB
C++
#pragma once
|
|
|
|
#include <optional>
|
|
#include <vector>
|
|
#include <chrono>
|
|
#include <sstream>
|
|
|
|
#include "json/json.hpp"
|
|
|
|
#include "progress.hpp"
|
|
#include "extension.hpp"
|
|
|
|
namespace cakefoot
|
|
{
|
|
class Replay
|
|
{
|
|
|
|
public:
|
|
|
|
const static int time_precision {100'000};
|
|
const static int position_precision {10'000};
|
|
|
|
enum Event
|
|
{
|
|
none = 0,
|
|
collision = 1,
|
|
coin = 3,
|
|
checkpoint = 4,
|
|
collect = 5,
|
|
end = 6
|
|
};
|
|
|
|
struct KeyFrame
|
|
{
|
|
float time;
|
|
glm::vec2 translation;
|
|
bool mirrored = false;
|
|
Event event = none;
|
|
float speed = 0.0f;
|
|
};
|
|
|
|
/* Exposed for get and set by the client */
|
|
bool coin_taken = false;
|
|
bool coin_collected = false;
|
|
int checkpoints = 0;
|
|
|
|
protected:
|
|
|
|
/* Track where the data is stored. If not stored yet, the path will be empty. Protected so that it can be mocked
|
|
* in test programs. */
|
|
fs::path most_recent_path;
|
|
|
|
private:
|
|
|
|
sb::progress::Progress progress;
|
|
std::size_t playback_index = 0;
|
|
std::string key_frames_key {"key frames"};
|
|
std::string metadata_key {"metadata"};
|
|
std::string level_metadata_key {"level"};
|
|
std::string length_metadata_key {"length"};
|
|
std::string save_data_id_key {"save data id"};
|
|
std::string start_time_key {"start"};
|
|
KeyFrame _last_read;
|
|
bool end_event_encountered = false;
|
|
|
|
/*!
|
|
* @return All key frames stored in the progress object
|
|
*/
|
|
inline nlohmann::json& key_frames()
|
|
{
|
|
return progress.get(key_frames_key);
|
|
};
|
|
|
|
/*!
|
|
* @return All key frames stored in the progress object
|
|
*/
|
|
inline const nlohmann::json& key_frames() const
|
|
{
|
|
return progress.read(key_frames_key);
|
|
};
|
|
|
|
/*!
|
|
* @return The most recently stored key frame data in the progress object
|
|
*/
|
|
KeyFrame latest_key_frame() const;
|
|
|
|
/*!
|
|
* @param index Key frame index to check the time elapsed from the start of the replay
|
|
*
|
|
* @return Time elapsed since the start of the replay at this frame timestamp
|
|
*/
|
|
float elapsed(int index) const;
|
|
|
|
public:
|
|
|
|
/*!
|
|
* Read the frames of replay data contained in a JSON file into this object. Any data currently stored in the
|
|
* object will be overwritten.
|
|
*
|
|
* The path will be stored in the object, so that the next call to `Replay::remove` will delete this path,
|
|
* unless `Replay::save` or `Replay::load` are called first.
|
|
*
|
|
* @param path JSON file containing replay key frame data
|
|
*
|
|
* @throw nlohmann::json::parse_error If the file is unreadable as JSON
|
|
*/
|
|
void load(const fs::path& path);
|
|
|
|
/*!
|
|
* Add a key frame to the replay object. If this is the first frame, the given frame time will represent the
|
|
* zero time of the replay.
|
|
*
|
|
* @warning The end event is meant to only be recorded once per replay file, but currently there is nothing
|
|
* enforcing this.
|
|
*
|
|
* @param frame Key frame data to add
|
|
*/
|
|
void record(KeyFrame frame);
|
|
|
|
/*!
|
|
* Write the replay object's frames as JSON to a given directory. This always creates a new file.
|
|
*
|
|
* The file name will be generated using the format `{level}_{date}_{id}.json`. The file path will be stored in
|
|
* this object, so the next call to `Replay::remove()` will delete this path unless `Replay::save` or
|
|
* `Replay::load` are called first.
|
|
*
|
|
* The save data ID is an optional ID string that can associate the replay with a save progress file.
|
|
*
|
|
* @param level Index of the level the replay is for
|
|
* @param metadata Custom JSON data to store with the file
|
|
* @param directory Path to a directory to create the file in
|
|
* @param save_data_id Identifies the save progress file associated with this replay
|
|
*
|
|
* @return Path to the newly created file
|
|
*/
|
|
void save(
|
|
int level,
|
|
const nlohmann::json& metadata = {},
|
|
const fs::path& directory = ".",
|
|
const std::string save_data_id = "");
|
|
|
|
/*!
|
|
* Delete the data in storage for this replay. The data is the most recent stored with `Replay::save` or loaded
|
|
* with `Replay::load`. If there is no stored data associated with the replay, this function doesn't do
|
|
* anything.
|
|
*/
|
|
void remove();
|
|
|
|
/*!
|
|
* Get a vector containing all unread frames in the replay object up until a certain timestamp. This will
|
|
* increment the playback index, so a subsequent call using the same time parameter would return an empty list.
|
|
*
|
|
* @param time Get unread frames up to this timestamp in seconds
|
|
*
|
|
* @return A vector of key frames
|
|
*/
|
|
std::vector<KeyFrame> unread(float time);
|
|
|
|
/*!
|
|
* @return The most recently read frame, which is the same as the last frame in the list returned by the most
|
|
* recent call to Replay::unread.
|
|
*/
|
|
KeyFrame last_read() const;
|
|
|
|
/*!
|
|
* Reset playback index to zero, effectively marking all key frames as unread and restarting the replay.
|
|
*/
|
|
void reset();
|
|
|
|
/*!
|
|
* @return Length of the replay in seconds
|
|
*/
|
|
float length() const;
|
|
|
|
/*!
|
|
* @return True if the replay doesn't contain any frames, false otherwise.
|
|
*/
|
|
bool empty() const;
|
|
|
|
/*!
|
|
* @param frame Key frame to check the time elapsed from the start of the replay
|
|
*
|
|
* @return Time elapsed since the start of the replay at this frame timestamp
|
|
*/
|
|
float elapsed(KeyFrame frame) const;
|
|
|
|
/*!
|
|
* @return The ID associated with the replay's progress object and file
|
|
*/
|
|
std::string id() const;
|
|
|
|
/*!
|
|
* This will return true if the end event has been encounter during a read.
|
|
*
|
|
* @warning There is currently nothing preventing adding or reading frames past the end event, so this
|
|
* function basically assumes the end event is the last frame in the replay.
|
|
*/
|
|
bool ended() const;
|
|
|
|
/*!
|
|
* Get the metadata associated with the replay data, which can be any custom JSON data saved along with the
|
|
* replay file. This can be set when saving the replay data with Replay::save, using the metadata parameter.
|
|
* Before the replay is saved or loaded, this will always return an empty string.
|
|
*
|
|
* @return JSON metadata associated with the replay data
|
|
*/
|
|
nlohmann::json metadata() const;
|
|
|
|
/*!
|
|
* @return Read-only access to the replay data as a JSON object
|
|
*/
|
|
const nlohmann::json& json() const;
|
|
|
|
/*!
|
|
* Remove all frames from the replay. Replay::empty will return true afterward.
|
|
*/
|
|
void clear();
|
|
};
|
|
|
|
/*!
|
|
* Convert a KeyFrame object into a JSON array. This is called internally by the JSON library when
|
|
* Replay::save is called.
|
|
*
|
|
* @param json Array for storing KeyFrame data
|
|
* @param key_frame The object containing frame data to convert to JSON
|
|
*/
|
|
void to_json(nlohmann::json& json, const Replay::KeyFrame& key_frame);
|
|
|
|
/*!
|
|
* Convert a JSON array into a KeyFrame object. This is called internally by the JSON library when
|
|
* json::get<KeyFrame> is called.
|
|
*
|
|
* @param json Array containing KeyFrame data
|
|
* @param key_frame The object to store the data in
|
|
*/
|
|
void from_json(const nlohmann::json& json, Replay::KeyFrame& key_frame);
|
|
}
|