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
12namespace Kinoko {
13
16 ASSERT(m_currentGhostFileName);
17 ASSERT(m_currentRawGhost);
18 ASSERT(m_currentGhost);
19
20 auto *sceneCreator = new Host::SceneCreatorDynamic;
21 m_sceneMgr = new EGG::SceneManager(sceneCreator);
22
23 System::RaceConfig::RegisterInitCallback(OnInit, nullptr);
24 Abstract::File::Remove("results.txt");
25
26 m_sceneMgr->changeScene(0);
27}
28
31 m_sceneMgr->calc();
32}
33
38 while (!calcEnd()) {
39 calc();
40 }
41
42 return success();
43}
44
49void KReplaySystem::parseOptions(int argc, char **argv) {
50 if (argc < 2) {
51 PANIC("Expected ghost argument!");
52 }
53
54 for (int i = 0; i < argc; ++i) {
55 std::optional<Host::EOption> flag = Host::Option::CheckFlag(argv[i]);
56 if (!flag || *flag == Host::EOption::Invalid) {
57 WARN("Expected a flag! Got: %s", argv[i]);
58 continue;
59 }
60
61 switch (*flag) {
62 case Host::EOption::Ghost: {
63 ASSERT(i + 1 < argc);
64
65 m_currentGhostFileName = argv[++i];
66 m_currentRawGhost = Abstract::File::Load(m_currentGhostFileName, m_currentRawGhostSize);
67
68 if (m_currentRawGhostSize < System::RKG_HEADER_SIZE ||
69 m_currentRawGhostSize > sizeof(System::RawGhostFile)) {
70 PANIC("File cannot be a ghost! Check the file size.");
71 }
72
73 // Creating the raw ghost file validates it
74 System::RawGhostFile file = System::RawGhostFile(m_currentRawGhost);
75
76 m_currentGhost = new System::GhostFile(file);
77 ASSERT(m_currentGhost);
78 } break;
79 case Host::EOption::Invalid:
80 default:
81 PANIC("Invalid flag!");
82 break;
83 }
84 }
85}
86
87KReplaySystem *KReplaySystem::CreateInstance() {
88 ASSERT(!s_instance);
89 s_instance = new KReplaySystem;
90 return static_cast<KReplaySystem *>(s_instance);
91}
92
93void KReplaySystem::DestroyInstance() {
94 ASSERT(s_instance);
95 auto *instance = s_instance;
96 s_instance = nullptr;
97 delete instance;
98}
99
100KReplaySystem::KReplaySystem()
101 : m_currentGhostFileName(nullptr), m_currentGhost(nullptr), m_currentRawGhost(nullptr),
102 m_currentRawGhostSize(0) {}
103
104KReplaySystem::~KReplaySystem() {
105 if (s_instance) {
106 s_instance = nullptr;
107 WARN("KReplaySystem instance not explicitly handled!");
108 }
109
110 delete m_sceneMgr;
111 delete m_currentGhost;
112 delete m_currentRawGhost;
113}
114
118 constexpr u16 MAX_MINUTE_COUNT = 10;
119
120 const auto *raceManager = System::RaceManager::Instance();
121 if (raceManager->stage() == System::RaceManager::Stage::FinishGlobal) {
122 return true;
123 }
124
125 if (raceManager->timerManager().currentTimer().min >= MAX_MINUTE_COUNT) {
126 return true;
127 }
128
129 return false;
130}
131
134void KReplaySystem::reportFail(const std::string &msg) const {
135 std::string report(m_currentGhostFileName);
136 report += "\n" + std::string(msg);
137 Abstract::File::Append("results.txt", report.c_str(), report.size());
138}
139
143 auto format = [](const System::Timer &timer) {
144 std::ostringstream oss;
145 oss << std::setw(2) << std::setfill('0') << timer.min << ":" << std::setw(2)
146 << std::setfill('0') << timer.sec << "." << std::setw(3) << std::setfill('0')
147 << timer.mil;
148 return oss.str();
149 };
150
151 const auto *raceManager = System::RaceManager::Instance();
152 if (raceManager->stage() != System::RaceManager::Stage::FinishGlobal) {
153 m_sceneMgr->currentScene()->heap()->enableAllocation();
154 reportFail("Race didn't finish");
155 return false;
156 }
157
158 s32 desyncingTimerIdx = getDesyncingTimerIdx();
159 if (desyncingTimerIdx != -1) {
160 m_sceneMgr->currentScene()->heap()->enableAllocation();
161 std::string msg;
162
163 const auto [correct, incorrect] = getDesyncingTimer(desyncingTimerIdx);
164 if (desyncingTimerIdx == 0) {
165 msg = "Final timer desync!";
166 } else {
167 msg = "Lap " + std::to_string(desyncingTimerIdx) + " timer desync!";
168 }
169
170 msg += " Expected " + format(correct) + ", got " + format(incorrect);
171 reportFail(msg);
172 return false;
173 }
174
175 return true;
176}
177
181 const auto &player = System::RaceManager::Instance()->player();
182 if (m_currentGhost->raceTimer() != player.raceTimer()) {
183 return 0;
184 }
185
186 for (size_t i = 0; i < 3; ++i) {
187 if (m_currentGhost->lapTimer(i) != player.getLapSplit(i + 1)) {
188 return i + 1;
189 }
190 }
191
192 return -1;
193}
194
198KReplaySystem::DesyncingTimerPair KReplaySystem::getDesyncingTimer(s32 i) const {
199 auto cond = i <=> 0;
200 ASSERT(cond != std::strong_ordering::less);
201
202 if (cond == std::strong_ordering::equal) {
203 const auto &correct = m_currentGhost->raceTimer();
204 const auto &incorrect = System::RaceManager::Instance()->player().raceTimer();
205 ASSERT(correct != incorrect);
206 return DesyncingTimerPair(correct, incorrect);
207 } else if (cond == std::strong_ordering::greater) {
208 const auto &correct = m_currentGhost->lapTimer(i - 1);
209 const auto &incorrect = System::RaceManager::Instance()->player().lapTimer(i - 1);
210 ASSERT(correct != incorrect);
211 return DesyncingTimerPair(correct, incorrect);
212 }
213
214 // This is unreachable
215 return DesyncingTimerPair(System::Timer(), System::Timer());
216}
217
221void KReplaySystem::OnInit(System::RaceConfig *config, void * /* arg */) {
222 config->setGhost(Instance()->m_currentRawGhost);
223 config->raceScenario().players[0].type = System::RaceConfig::Player::Type::Ghost;
224}
225
226} // namespace Kinoko
Manages the scene stack and transitions between scenes.
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.
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: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.