There is a bug in the JSON library which causes the initializer list constructor to inconsistently create either an array or a scalar value. This bug causes inconsistent behavoir between the PC and WASM builds. See https://json.nlohmann.me/home/faq/#known-bugs
247 lines
6.7 KiB
C++
247 lines
6.7 KiB
C++
#include "Replay.hpp"
|
|
|
|
cakefoot::Replay::KeyFrame cakefoot::Replay::latest_key_frame() const
|
|
{
|
|
return progress.read(key_frames_key).back().get<KeyFrame>();
|
|
}
|
|
|
|
void cakefoot::Replay::load(const fs::path& path)
|
|
{
|
|
progress.load(path);
|
|
most_recent_path = path;
|
|
}
|
|
|
|
void cakefoot::Replay::record(KeyFrame frame)
|
|
{
|
|
/* The key frames array will automatically be created by nlohmann::json if it doesn't exist */
|
|
key_frames().push_back(frame);
|
|
}
|
|
|
|
void cakefoot::Replay::save(
|
|
int level,
|
|
const nlohmann::json& metadata,
|
|
const fs::path& directory,
|
|
const std::string save_data_id)
|
|
{
|
|
std::ostringstream file_name;
|
|
|
|
/* The file name is the level index followed by a time stamp followed by the file ID. */
|
|
file_name << std::setfill('0') << std::setw(2) << level << "_" <<
|
|
sb::time::minute_stamp(std::chrono::system_clock::now()) << "_" << progress.id() << ".json";
|
|
fs::path path { directory / file_name.str() };
|
|
sb::Log::Multi() << "Writing replay to " << path << sb::Log::end;
|
|
|
|
/* Add metadata from user if requested. Add level index and length to the metadata. */
|
|
progress.set(metadata, metadata_key);
|
|
progress.set(level, metadata_key, level_metadata_key);
|
|
std::ostringstream length_formatted;
|
|
length_formatted << std::setprecision(2) << length();
|
|
progress.set(length_formatted.str(), metadata_key, length_metadata_key);
|
|
|
|
/* Associate with a save data file if ID is given */
|
|
if (!save_data_id.empty())
|
|
{
|
|
progress.set(save_data_id, save_data_id_key);
|
|
}
|
|
|
|
/* Add the start time to the file */
|
|
std::chrono::time_point<std::chrono::system_clock> start_time {
|
|
std::chrono::system_clock::now() - std::chrono::seconds(static_cast<int>(length()))
|
|
};
|
|
progress.set(sb::time::minute_stamp(start_time), start_time_key);
|
|
|
|
/* Progress::save takes care of creating the directory if necessary */
|
|
progress.save(path, true, false);
|
|
|
|
/* Save the path, so data can be removed if necessary */
|
|
most_recent_path = path;
|
|
}
|
|
|
|
void cakefoot::Replay::remove()
|
|
{
|
|
if (fs::exists(most_recent_path))
|
|
{
|
|
try
|
|
{
|
|
fs::remove(most_recent_path);
|
|
sb::Log::Multi() << "Removed replay at " << most_recent_path << sb::Log::end;
|
|
}
|
|
catch (fs::filesystem_error error)
|
|
{
|
|
sb::Log::Multi() << "Error removing replay at " << most_recent_path << ": " << error.what() <<
|
|
sb::Log::end;
|
|
}
|
|
}
|
|
most_recent_path.clear();
|
|
}
|
|
|
|
std::vector<cakefoot::Replay::KeyFrame> cakefoot::Replay::unread(float time)
|
|
{
|
|
std::vector<KeyFrame> frames;
|
|
|
|
/* Get a list of all unread frames until the given time stamp */
|
|
while (playback_index < key_frames().size() && elapsed(playback_index) <= time)
|
|
{
|
|
const KeyFrame& frame { key_frames().at(playback_index++).get<KeyFrame>() };
|
|
frames.push_back(frame);
|
|
|
|
/* Mark event progress */
|
|
if (frame.event == coin)
|
|
{
|
|
coin_taken = true;
|
|
}
|
|
else if (frame.event == collect)
|
|
{
|
|
coin_collected = true;
|
|
}
|
|
else if (frame.event == collision && !coin_collected)
|
|
{
|
|
coin_taken = false;
|
|
}
|
|
else if (frame.event == checkpoint)
|
|
{
|
|
checkpoints++;
|
|
}
|
|
else if (frame.event == end)
|
|
{
|
|
end_event_encountered = true;
|
|
}
|
|
}
|
|
|
|
if (!frames.empty())
|
|
{
|
|
/* Calculate the speed based on movement between frames */
|
|
if (playback_index > 1 && frames.back().event != end)
|
|
{
|
|
KeyFrame previous { key_frames().at(playback_index - 2).get<KeyFrame>() };
|
|
int mirror { frames.back().mirrored ? -1 : 1 };
|
|
float speed {
|
|
(mirror * (frames.back().translation.x - previous.translation.x) >= 0.0f ? 1 : -1)
|
|
* glm::distance(frames.back().translation, previous.translation)
|
|
* (1.0f / std::max(frames.back().time - previous.time, 0.0001f))
|
|
};
|
|
frames.back().speed = speed;
|
|
}
|
|
else
|
|
{
|
|
frames.back().speed = 0.0f;
|
|
}
|
|
|
|
/* Store the most recently read frame for fast access */
|
|
_last_read = frames.back();
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
cakefoot::Replay::KeyFrame cakefoot::Replay::last_read() const
|
|
{
|
|
return _last_read;
|
|
}
|
|
|
|
void cakefoot::Replay::reset()
|
|
{
|
|
playback_index = 0;
|
|
coin_taken = false;
|
|
coin_collected = false;
|
|
checkpoints = 0;
|
|
end_event_encountered = false;
|
|
}
|
|
|
|
float cakefoot::Replay::length() const
|
|
{
|
|
if (!empty())
|
|
{
|
|
return key_frames().at(key_frames().size() - 1).get<KeyFrame>().time -
|
|
key_frames().front().get<KeyFrame>().time;
|
|
}
|
|
else
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
bool cakefoot::Replay::empty() const
|
|
{
|
|
return !progress.contains(key_frames_key) || key_frames().size() == 0;
|
|
}
|
|
|
|
float cakefoot::Replay::elapsed(int index) const
|
|
{
|
|
return length() > 0.0f ? elapsed(key_frames().at(index).get<KeyFrame>()) : 0.0f;
|
|
}
|
|
|
|
float cakefoot::Replay::elapsed(KeyFrame frame) const
|
|
{
|
|
return length() > 0.0f ? frame.time - key_frames().front().get<KeyFrame>().time : 0.0f;
|
|
}
|
|
|
|
std::string cakefoot::Replay::id() const
|
|
{
|
|
return progress.id();
|
|
}
|
|
|
|
bool cakefoot::Replay::ended() const
|
|
{
|
|
return end_event_encountered;
|
|
}
|
|
|
|
nlohmann::json cakefoot::Replay::metadata() const
|
|
{
|
|
if (progress.contains(metadata_key))
|
|
{
|
|
return progress.read(metadata_key);
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const nlohmann::json& cakefoot::Replay::json() const
|
|
{
|
|
return progress.read();
|
|
}
|
|
|
|
void cakefoot::Replay::clear()
|
|
{
|
|
key_frames().clear();
|
|
}
|
|
|
|
void cakefoot::to_json(nlohmann::json& json, const Replay::KeyFrame& key_frame)
|
|
{
|
|
nlohmann::json serial = nlohmann::json({
|
|
int(Replay::time_precision * key_frame.time),
|
|
int(Replay::position_precision * key_frame.translation.x),
|
|
int(Replay::position_precision * key_frame.translation.y)});
|
|
|
|
if (key_frame.mirrored || key_frame.event != Replay::Event::none)
|
|
{
|
|
serial.push_back(int(key_frame.mirrored));
|
|
}
|
|
if (key_frame.event != Replay::Event::none)
|
|
{
|
|
serial.push_back(key_frame.event);
|
|
}
|
|
json = serial;
|
|
}
|
|
|
|
void cakefoot::from_json(const nlohmann::json& json, Replay::KeyFrame& key_frame)
|
|
{
|
|
float time {float(json.at(0)) / Replay::time_precision};
|
|
glm::vec2 position = glm::fvec2{json.at(1), json.at(2)} / glm::fvec2{Replay::position_precision};
|
|
|
|
if (json.size() == 3)
|
|
{
|
|
key_frame = Replay::KeyFrame{time, position};
|
|
}
|
|
else if (json.size() == 4)
|
|
{
|
|
key_frame = Replay::KeyFrame{time, position, bool(json.at(3).get<int>())};
|
|
}
|
|
else
|
|
{
|
|
key_frame = Replay::KeyFrame{time, position, bool(json.at(3).get<int>()), json.at(4)};
|
|
}
|
|
}
|