A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KReplaySystem.cc
1#include "KReplaySystem.hh"
2
3#include "host/Option.hh"
4#include "host/SceneCreatorDynamic.hh"
5
6#include <abstract/File.hh>
7
8#include <game/system/RaceManager.hh>
9
10#include <iomanip>
11
14 ASSERT(m_currentGhostFileName);
15 ASSERT(m_currentRawGhost);
16 ASSERT(m_currentGhost);
17
18 auto *sceneCreator = new Host::SceneCreatorDynamic;
19 m_sceneMgr = new EGG::SceneManager(sceneCreator);
20
21 System::RaceConfig::RegisterInitCallback(OnInit, nullptr);
22 Abstract::File::Remove("results.txt");
23
24 m_sceneMgr->changeScene(0);
25}
26
29 m_sceneMgr->calc();
30}
31
36 while (!calcEnd()) {
37 calc();
38 }
39
40 return success();
41}
42
47void KReplaySystem::parseOptions(int argc, char **argv) {
48 if (argc < 2) {
49 PANIC("Expected ghost argument!");
50 }
51
52 for (int i = 0; i < argc; ++i) {
53 std::optional<Host::EOption> flag = Host::Option::CheckFlag(argv[i]);
54 if (!flag || *flag == Host::EOption::Invalid) {
55 WARN("Expected a flag! Got: %s", argv[i]);
56 continue;
57 }
58
59 switch (*flag) {
60 case Host::EOption::Ghost: {
61 ASSERT(i + 1 < argc);
62
63 m_currentGhostFileName = argv[++i];
64 m_currentRawGhost = Abstract::File::Load(m_currentGhostFileName, m_currentRawGhostSize);
65
66 if (m_currentRawGhostSize < System::RKG_HEADER_SIZE ||
67 m_currentRawGhostSize > sizeof(System::RawGhostFile)) {
68 PANIC("File cannot be a ghost! Check the file size.");
69 }
70
71 // Creating the raw ghost file validates it
72 System::RawGhostFile file = System::RawGhostFile(m_currentRawGhost);
73
74 m_currentGhost = new System::GhostFile(file);
75 ASSERT(m_currentGhost);
76 } break;
77 case Host::EOption::Invalid:
78 default:
79 PANIC("Invalid flag!");
80 break;
81 }
82 }
83}
84
85KReplaySystem *KReplaySystem::CreateInstance() {
86 ASSERT(!s_instance);
87 s_instance = new KReplaySystem;
88 return static_cast<KReplaySystem *>(s_instance);
89}
90
91void KReplaySystem::DestroyInstance() {
92 ASSERT(s_instance);
93 auto *instance = s_instance;
94 s_instance = nullptr;
95 delete instance;
96}
97
98KReplaySystem::KReplaySystem()
99 : m_currentGhostFileName(nullptr), m_currentGhost(nullptr), m_currentRawGhost(nullptr),
100 m_currentRawGhostSize(0) {}
101
102KReplaySystem::~KReplaySystem() {
103 if (s_instance) {
104 s_instance = nullptr;
105 WARN("KReplaySystem instance not explicitly handled!");
106 }
107
108 delete m_sceneMgr;
109 delete m_currentGhost;
110 delete m_currentRawGhost;
111}
112
116 constexpr u16 MAX_MINUTE_COUNT = 10;
117
118 const auto *raceManager = System::RaceManager::Instance();
119 if (raceManager->stage() == System::RaceManager::Stage::FinishGlobal) {
120 return true;
121 }
122
123 if (raceManager->timerManager().currentTimer().min >= MAX_MINUTE_COUNT) {
124 return true;
125 }
126
127 return false;
128}
129
132void KReplaySystem::reportFail(const std::string &msg) const {
133 std::string report(m_currentGhostFileName);
134 report += "\n" + std::string(msg);
135 Abstract::File::Append("results.txt", report.c_str(), report.size());
136}
137
141 auto format = [](const System::Timer &timer) {
142 std::ostringstream oss;
143 oss << std::setw(2) << std::setfill('0') << timer.min << ":" << std::setw(2)
144 << std::setfill('0') << timer.sec << "." << std::setw(3) << std::setfill('0')
145 << timer.mil;
146 return oss.str();
147 };
148
149 const auto *raceManager = System::RaceManager::Instance();
150 if (raceManager->stage() != System::RaceManager::Stage::FinishGlobal) {
151 m_sceneMgr->currentScene()->heap()->enableAllocation();
152 reportFail("Race didn't finish");
153 return false;
154 }
155
156 s32 desyncingTimerIdx = getDesyncingTimerIdx();
157 if (desyncingTimerIdx != -1) {
158 m_sceneMgr->currentScene()->heap()->enableAllocation();
159 std::string msg;
160
161 const auto [correct, incorrect] = getDesyncingTimer(desyncingTimerIdx);
162 if (desyncingTimerIdx == 0) {
163 msg = "Final timer desync!";
164 } else {
165 msg = "Lap " + std::to_string(desyncingTimerIdx) + " timer desync!";
166 }
167
168 msg += " Expected " + format(correct) + ", got " + format(incorrect);
169 reportFail(msg);
170 return false;
171 }
172
173 return true;
174}
175
179 const auto &player = System::RaceManager::Instance()->player();
180 if (m_currentGhost->raceTimer() != player.raceTimer()) {
181 return 0;
182 }
183
184 for (size_t i = 0; i < 3; ++i) {
185 if (m_currentGhost->lapTimer(i) != player.getLapSplit(i + 1)) {
186 return i + 1;
187 }
188 }
189
190 return -1;
191}
192
196KReplaySystem::DesyncingTimerPair KReplaySystem::getDesyncingTimer(s32 i) const {
197 auto cond = i <=> 0;
198 ASSERT(cond != std::strong_ordering::less);
199
200 if (cond == std::strong_ordering::equal) {
201 const auto &correct = m_currentGhost->raceTimer();
202 const auto &incorrect = System::RaceManager::Instance()->player().raceTimer();
203 ASSERT(correct != incorrect);
204 return DesyncingTimerPair(correct, incorrect);
205 } else if (cond == std::strong_ordering::greater) {
206 const auto &correct = m_currentGhost->lapTimer(i - 1);
207 const auto &incorrect = System::RaceManager::Instance()->player().lapTimer(i - 1);
208 ASSERT(correct != incorrect);
209 return DesyncingTimerPair(correct, incorrect);
210 }
211
212 // This is unreachable
213 return DesyncingTimerPair(System::Timer(), System::Timer());
214}
215
219void KReplaySystem::OnInit(System::RaceConfig *config, void * /* arg */) {
220 config->setGhost(Instance()->m_currentRawGhost);
221 config->raceScenario().players[0].type = System::RaceConfig::Player::Type::Ghost;
222}
Manages the scene stack and transitions between scenes.
Kinoko system designed to execute replays.
bool success() const
Determines whether the simulation was a success or not.
bool calcEnd() const
Determines whether or not the ghost simulation should end.
void reportFail(const std::string &msg) const
Reports failure to file.
void parseOptions(int argc, char **argv) override
Parses non-generic command line options.
static void OnInit(System::RaceConfig *config, void *arg)
Initializes the race configuration as needed for replays.
void init() override
Initializes the system.
s32 getDesyncingTimerIdx() const
Finds the desyncing timer index, if one exists.
DesyncingTimerPair getDesyncingTimer(s32 i) const
Gets the desyncing timer according to the index.
bool run() override
Executes a run.
void calc() override
Executes a frame.
Parsed representation of a binary ghost file.
Definition GhostFile.hh:77
Initializes the player with parameters specified in the provided ghost file.
Definition RaceConfig.hh:15
The binary data of a ghost saved to a file.
Definition GhostFile.hh:45
A simple struct to represent a lap or race finish time.