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#include <egg/core/Heap.hh>
8
9#include <game/system/RaceManager.hh>
10
11#include <iomanip>
12
13namespace Kinoko {
14
17 ASSERT(m_currentGhostFileName);
18 ASSERT(m_currentRawGhost);
19 ASSERT(m_currentGhost);
20
21 auto *sceneCreator = EGG::egg_new<Host::SceneCreatorDynamic>();
22 m_sceneMgr = EGG::egg_new<EGG::SceneManager>(sceneCreator);
23
24 System::RaceConfig::RegisterInitCallback(OnInit, nullptr);
25 Abstract::File::Remove("results.txt");
26
27 m_sceneMgr->changeScene(0);
28}
29
32 m_sceneMgr->calc();
33}
34
39 while (!calcEnd()) {
40 calc();
41 }
42
43 return success();
44}
45
50void KReplaySystem::parseOptions(int argc, char **argv) {
51 if (argc < 2) {
52 PANIC("Expected ghost argument!");
53 }
54
55 for (int i = 0; i < argc; ++i) {
56 std::optional<Host::EOption> flag = Host::Option::CheckFlag(argv[i]);
57 if (!flag || *flag == Host::EOption::Invalid) {
58 WARN("Expected a flag! Got: %s", argv[i]);
59 continue;
60 }
61
62 switch (*flag) {
63 case Host::EOption::Ghost: {
64 ASSERT(i + 1 < argc);
65
66 m_currentGhostFileName = argv[++i];
67 m_currentRawGhost =
68 Abstract::File::LoadHost(m_currentGhostFileName, m_currentRawGhostSize);
69
70 if (m_currentRawGhostSize < System::RKG_HEADER_SIZE ||
71 m_currentRawGhostSize > sizeof(System::RawGhostFile)) {
72 PANIC("File cannot be a ghost! Check the file size.");
73 }
74
75 // Creating the raw ghost file validates it
76 System::RawGhostFile file = System::RawGhostFile(m_currentRawGhost);
77
78 m_currentGhost = EGG::egg_new<System::GhostFile>(file);
79 ASSERT(m_currentGhost);
80 } break;
81 case Host::EOption::Invalid:
82 default:
83 PANIC("Invalid flag!");
84 break;
85 }
86 }
87}
88
89KReplaySystem *KReplaySystem::CreateInstance() {
90 ASSERT(!s_instance);
91 s_instance = EGG::egg_new<KReplaySystem>();
92 return static_cast<KReplaySystem *>(s_instance);
93}
94
95void KReplaySystem::DestroyInstance() {
96 ASSERT(s_instance);
97 auto *instance = s_instance;
98 s_instance = nullptr;
99 EGG::egg_delete(instance);
100}
101
102KReplaySystem::KReplaySystem()
103 : m_currentGhostFileName(nullptr), m_currentGhost(nullptr), m_currentRawGhost(nullptr),
104 m_currentRawGhostSize(0) {}
105
106KReplaySystem::~KReplaySystem() {
107 if (s_instance) {
108 s_instance = nullptr;
109 WARN("KReplaySystem instance not explicitly handled!");
110 }
111
112 EGG::egg_delete(m_sceneMgr);
113 EGG::egg_delete(m_currentGhost);
114 delete[] m_currentRawGhost;
115}
116
120 constexpr u16 MAX_MINUTE_COUNT = 10;
121
122 const auto *raceManager = System::RaceManager::Instance();
123 if (raceManager->stage() == System::RaceManager::Stage::FinishGlobal) {
124 return true;
125 }
126
127 if (raceManager->timerManager().currentTimer().min >= MAX_MINUTE_COUNT) {
128 return true;
129 }
130
131 return false;
132}
133
136void KReplaySystem::reportFail(const std::string &msg) const {
137 std::string report(m_currentGhostFileName);
138 report += "\n" + std::string(msg);
139 Abstract::File::Append("results.txt", report.c_str(), report.size());
140}
141
145 auto format = [](const System::Timer &timer) {
146 std::ostringstream oss;
147 oss << std::setw(2) << std::setfill('0') << timer.min << ":" << std::setw(2)
148 << std::setfill('0') << timer.sec << "." << std::setw(3) << std::setfill('0')
149 << timer.mil;
150 return oss.str();
151 };
152
153 const auto *raceManager = System::RaceManager::Instance();
154 if (raceManager->stage() != System::RaceManager::Stage::FinishGlobal) {
155 m_sceneMgr->currentScene()->heap()->enableAllocation();
156 reportFail("Race didn't finish");
157 return false;
158 }
159
160 s32 desyncingTimerIdx = getDesyncingTimerIdx();
161 if (desyncingTimerIdx != -1) {
162 m_sceneMgr->currentScene()->heap()->enableAllocation();
163 std::string msg;
164
165 const auto [correct, incorrect] = getDesyncingTimer(desyncingTimerIdx);
166 if (desyncingTimerIdx == 0) {
167 msg = "Final timer desync!";
168 } else {
169 msg = "Lap " + std::to_string(desyncingTimerIdx) + " timer desync!";
170 }
171
172 msg += " Expected " + format(correct) + ", got " + format(incorrect);
173 reportFail(msg);
174 return false;
175 }
176
177 return true;
178}
179
183 const auto &player = System::RaceManager::Instance()->player();
184 if (m_currentGhost->raceTimer() != player.raceTimer()) {
185 return 0;
186 }
187
188 for (size_t i = 0; i < 3; ++i) {
189 if (m_currentGhost->lapTimer(i) != player.getLapSplit(i + 1)) {
190 return i + 1;
191 }
192 }
193
194 return -1;
195}
196
200KReplaySystem::DesyncingTimerPair KReplaySystem::getDesyncingTimer(s32 i) const {
201 auto cond = i <=> 0;
202 ASSERT(cond != std::strong_ordering::less);
203
204 if (cond == std::strong_ordering::equal) {
205 const auto &correct = m_currentGhost->raceTimer();
206 const auto &incorrect = System::RaceManager::Instance()->player().raceTimer();
207 ASSERT(correct != incorrect);
208 return DesyncingTimerPair(correct, incorrect);
209 } else if (cond == std::strong_ordering::greater) {
210 const auto &correct = m_currentGhost->lapTimer(i - 1);
211 const auto &incorrect = System::RaceManager::Instance()->player().lapTimer(i - 1);
212 ASSERT(correct != incorrect);
213 return DesyncingTimerPair(correct, incorrect);
214 }
215
216 // This is unreachable
217 return DesyncingTimerPair(System::Timer(), System::Timer());
218}
219
223void KReplaySystem::OnInit(System::RaceConfig *config, void * /* arg */) {
224 config->setGhost(Instance()->m_currentRawGhost);
225 config->raceScenario().players[0].type = System::RaceConfig::Player::Type::Ghost;
226}
227
228} // namespace Kinoko
Kinoko system designed to execute replays.
s32 getDesyncingTimerIdx() const
Finds the desyncing timer index, if one exists.
void parseOptions(int argc, char **argv) override
Parses non-generic command line options.
void reportFail(const std::string &msg) const
Reports failure to file.
bool calcEnd() const
Determines whether or not the ghost simulation should end.
bool run() override
Executes a run.
static void OnInit(System::RaceConfig *config, void *arg)
Initializes the race configuration as needed for replays.
DesyncingTimerPair getDesyncingTimer(s32 i) const
Gets the desyncing timer according to the index.
bool success() const
Determines whether the simulation was a success or not.
void calc() override
Executes a frame.
void init() override
Initializes the system.
Initializes the player with parameters specified in the provided ghost file.
Definition RaceConfig.hh:23
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.