Skip to content

Commit 7f2b333

Browse files
authored
Merge pull request #81 from fs-c/perf-testing-1
Fix randomization/humanization bugs and minor improvements
2 parents 95485c7 + 5dd776e commit 7f2b333

File tree

12 files changed

+372
-276
lines changed

12 files changed

+372
-276
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ _The following is only relevant to developers looking to build the project from
3636
# Get the code
3737
git clone https://github.com/fs-c/maniac.git
3838
39+
# Get submodule dependencies
40+
git submodule init
41+
git submodule update
42+
3943
# Building out-of-source is preferred
4044
mkdir build
4145
cd build
@@ -68,7 +72,8 @@ Of course please also feel free to open an issue for any questions you may have.
6872

6973
## Thanks
7074

71-
- n0b453c0d3r on UC and [mrflashstudio](https://github.com/mrflashstudio) for
72-
providing up to date signatures and offsets
73-
- [ocornut](https://github.com/ocornut) for the [imgui](https://github.com/ocornut/imgui) library
75+
- n0b453c0d3r on UC, [mrflashstudio](https://github.com/mrflashstudio) and [daycheat](https://github.com/daycheat) for
76+
providing up-to-date signatures and offsets and helpful discussion
77+
- LSNM for particularly helpful suggestions and bug reports
78+
- [ocornut](https://github.com/ocornut) for the [imgui](https://github.com/ocornut/imgui) library
7479
- to everyone who reported bugs and provided feedback

app/app.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ static void set_priority_class(int priority) {
3333
int main(int, char **) {
3434
std::string message;
3535

36+
maniac::config.read_from_file();
37+
3638
auto run = [&message](osu::Osu &osu) {
3739
maniac::osu = &osu;
3840

@@ -42,32 +44,32 @@ int main(int, char **) {
4244

4345
message = "found beatmap";
4446

45-
std::vector<osu::Action> actions;
47+
std::vector<osu::HitObject> hit_objects;
4648

4749
for (int i = 0; i < 10; i++) {
4850
try {
49-
actions = maniac::get_actions(osu.get_game_time());
51+
hit_objects = osu.get_hit_objects();
5052

5153
break;
5254
} catch (std::exception &err) {
53-
debug("get actions attempt %d failed: %s", i + 1, err.what());
55+
debug("get hit objects attempt %d failed: %s", i + 1, err.what());
5456

5557
std::this_thread::sleep_for(std::chrono::milliseconds(200));
5658
}
5759
}
5860

59-
if (actions.empty()) {
60-
throw std::runtime_error("failed getting actions");
61+
if (hit_objects.empty()) {
62+
throw std::runtime_error("failed getting hit objects");
6163
}
6264

6365
set_priority_class(HIGH_PRIORITY_CLASS);
6466

65-
maniac::randomize(actions, maniac::config.randomization_range);
66-
maniac::humanize(actions, maniac::config.humanization_modifier);
67+
maniac::randomize(hit_objects, maniac::config.randomization_range);
68+
maniac::humanize(hit_objects, maniac::config.humanization_modifier);
6769

6870
message = "playing";
6971

70-
maniac::play(actions);
72+
maniac::play(maniac::to_actions(hit_objects, osu.get_game_time()));
7173

7274
set_priority_class(NORMAL_PRIORITY_CLASS);
7375
};
@@ -123,6 +125,8 @@ int main(int, char **) {
123125
ImGui::End();
124126
});
125127

128+
maniac::config.write_to_file();
129+
126130
thread.request_stop();
127131

128132
return EXIT_SUCCESS;

lib/humanization.cpp

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,61 @@
11
#include <maniac/common.h>
22
#include <maniac/maniac.h>
33

4-
void maniac::randomize(std::vector<osu::Action> &actions, std::pair<int, int> range) {
4+
void maniac::randomize(std::vector<osu::HitObject> &hit_objects, std::pair<int, int> range) {
55
if (!range.first && !range.second)
66
return;
77

88
std::random_device rd;
99
std::mt19937 gen(rd());
10+
1011
std::uniform_int_distribution<> distr(range.first, range.second);
1112

12-
for (auto &action : actions) {
13-
action.time += distr(gen);
13+
for (auto &hit_object : hit_objects) {
14+
// if it's a slider we want to randomize start and end, if it's not we ignore end anyway
15+
hit_object.start_time += distr(gen);
16+
hit_object.end_time += distr(gen);
1417
}
1518

16-
debug("randomized %d actions with a range of [%d, %d]", actions.size(), range.first,
19+
debug("randomized %d hit objects with a range of [%d, %d]", hit_objects.size(), range.first,
1720
range.second);
1821
}
1922

20-
static std::vector<int> actions_per_frame(const std::vector<osu::Action> &actions,
21-
int time_frame = 1000) {
22-
std::vector<int> frames = {};
23-
const size_t chunks_needed = (actions.back().time / time_frame) + 1;
24-
25-
debug("will need %d frames", chunks_needed);
26-
27-
for (size_t chunk_i = 0; chunk_i < chunks_needed; chunk_i++) {
28-
frames.emplace_back(0);
29-
30-
for (const auto &action : actions) {
31-
const int32_t lower_bound = time_frame * chunk_i;
32-
const int32_t upper_bound = lower_bound + time_frame;
33-
34-
if (action.time >= lower_bound && action.time <= upper_bound) {
35-
frames[chunk_i]++;
36-
}
37-
}
38-
}
39-
40-
return frames;
41-
}
42-
43-
void maniac::humanize(std::vector<osu::Action> &actions, int modifier) {
44-
if (!modifier)
45-
return;
23+
void maniac::humanize(std::vector<osu::HitObject> &hit_objects, int modifier) {
24+
if (!modifier) {
25+
return;
26+
}
4627

4728
const auto actual_modifier = static_cast<double>(modifier) / 100.0;
4829

49-
constexpr auto frame_range = 1000;
50-
const auto frames = actions_per_frame(actions, frame_range);
30+
// count number of hits/unit of time (slice size)
31+
constexpr auto slice_size = 1000;
5132

52-
std::random_device rd;
53-
std::mt19937 gen(rd());
33+
const auto latest_hit = std::max_element(hit_objects.begin(), hit_objects.end(), [](auto a, auto b) {
34+
return a.end_time < b.end_time;
35+
})->end_time;
5436

55-
std::uniform_int_distribution<> offset_distr(-5, 5);
37+
auto slices = std::vector<int>{};
38+
slices.resize((latest_hit / slice_size) + 1);
5639

57-
const auto frames_size = frames.size();
58-
for (auto &action : actions) {
59-
const auto random_offset = offset_distr(gen);
60-
const size_t frame_i = action.time / frame_range;
40+
for (const auto &hit_object : hit_objects) {
41+
slices.at(hit_object.start_time / slice_size)++;
6142

62-
if (frame_i >= frames_size) {
63-
debug("ignoring invalid frame_i (%d)", frame_i);
43+
if (hit_object.is_slider) {
44+
slices.at(hit_object.end_time / slice_size)++;
45+
}
46+
}
6447

65-
continue;
66-
}
48+
for (auto &hit_object : hit_objects) {
49+
const auto start_offset = static_cast<int>(slices.at(hit_object.start_time / slice_size) * actual_modifier);
6750

68-
const int offset = static_cast<int>(frames.at(frame_i) * actual_modifier) + random_offset;
51+
hit_object.start_time += start_offset;
6952

70-
action.time += offset;
71-
}
53+
if (hit_object.is_slider) {
54+
const auto end_offset = static_cast<int>(slices.at(hit_object.end_time / slice_size) * actual_modifier);
55+
56+
hit_object.end_time += end_offset;
57+
}
58+
}
7259

73-
debug("%s %d %s %d %s %dms %s %f", "humanized", actions.size(), "actions over",
74-
frames_size, "time frames of", frame_range, "with a modifier of", actual_modifier);
60+
debug("humanized %d hit objects (%d slices of %dms) with modifier %d", hit_objects.size(), slices.size(), slice_size, modifier);
7561
}

lib/include/maniac/maniac.h

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,97 @@
44
#include <utility>
55
#include <maniac/osu.h>
66
#include <maniac/common.h>
7+
#include <fstream>
78

89
namespace maniac {
910
struct config {
1011
int tap_time = 20;
1112
bool mirror_mod = false;
12-
int compensation_offset = 0;
13+
int compensation_offset = -15;
1314
int humanization_modifier = 0;
1415
std::pair<int, int> randomization_range = { 0, 0 };
16+
17+
// TODO: This isn't configurable yet, use a non-shit config format
18+
std::string keys = "asdfjkl;";
19+
20+
// TODO: Would be good to have the read/write stuff be in a constructor/destructor
21+
22+
void read_from_file() {
23+
std::fstream file{"maniac-config", std::fstream::binary | std::fstream::in};
24+
25+
if (!file.is_open()) {
26+
debug("couldn't open config file for reading");
27+
28+
return;
29+
}
30+
31+
file.read(reinterpret_cast<char *>(&tap_time), sizeof tap_time);
32+
file.read(reinterpret_cast<char *>(&mirror_mod), sizeof mirror_mod);
33+
file.read(reinterpret_cast<char *>(&compensation_offset), sizeof compensation_offset);
34+
file.read(reinterpret_cast<char *>(&humanization_modifier), sizeof humanization_modifier);
35+
file.read(reinterpret_cast<char *>(&randomization_range.first), sizeof randomization_range.first);
36+
file.read(reinterpret_cast<char *>(&randomization_range.second), sizeof randomization_range.second);
37+
38+
debug("loaded config from file (%s)", keys.c_str());
39+
}
40+
41+
void write_to_file() {
42+
std::fstream file{"maniac-config", std::fstream::binary | std::fstream::trunc | std::fstream::out};
43+
44+
if (!file.is_open()) {
45+
debug("couldn't open config file for writing");
46+
47+
return;
48+
}
49+
50+
file.write(reinterpret_cast<char *>(&tap_time), sizeof tap_time);
51+
file.write(reinterpret_cast<char *>(&mirror_mod), sizeof mirror_mod);
52+
file.write(reinterpret_cast<char *>(&compensation_offset), sizeof compensation_offset);
53+
file.write(reinterpret_cast<char *>(&humanization_modifier), sizeof humanization_modifier);
54+
file.write(reinterpret_cast<char *>(&randomization_range.first), sizeof randomization_range.first);
55+
file.write(reinterpret_cast<char *>(&randomization_range.second), sizeof randomization_range.second);
56+
57+
debug("wrote config to file");
58+
}
1559
};
1660

61+
struct Action {
62+
char key;
63+
bool down;
64+
int32_t time;
65+
66+
short scan_code;
67+
68+
Action(char key, bool down, int32_t time) : key(key), down(down), time(time) {
69+
static auto layout = GetKeyboardLayout(0);
70+
71+
scan_code = VkKeyScanEx(key, layout) & 0xFF;
72+
};
73+
74+
bool operator < (const Action &action) const {
75+
return time < action.time;
76+
};
77+
78+
bool operator == (const Action &action) const {
79+
return action.key == key && action.down == down
80+
&& action.time == time;
81+
};
82+
83+
inline void execute() const {
84+
Process::send_scan_code(scan_code, down);
85+
}
86+
};
87+
1788
inline config config;
1889

1990
inline osu::Osu *osu;
2091

21-
void play(std::vector<osu::Action> &actions);
92+
void play(const std::vector<Action> &&actions);
2293

2394
void block_until_playing();
2495

25-
void humanize(std::vector<osu::Action> &actions, int modifier);
26-
void randomize(std::vector<osu::Action> &actions, std::pair<int, int> range);
96+
void humanize(std::vector<osu::HitObject> &hit_objects, int modifier);
97+
void randomize(std::vector<osu::HitObject> &hit_objects, std::pair<int, int> range);
2798

28-
std::vector<osu::Action> get_actions(int32_t min_time);
99+
std::vector<Action> to_actions(std::vector<osu::HitObject> &hit_objects, int32_t min_time);
29100
}

lib/include/maniac/osu.h

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,7 @@ namespace osu {
1414
#include <maniac/osu/internal.h>
1515
};
1616

17-
struct Action {
18-
char key;
19-
bool down;
20-
int32_t time;
21-
22-
short scan_code;
23-
24-
Action(char key, bool down, int32_t time) : key(key), down(down), time(time) {
25-
static auto layout = GetKeyboardLayout(0);
26-
27-
scan_code = VkKeyScanEx(key, layout) & 0xFF;
28-
};
29-
30-
bool operator < (const Action &action) const {
31-
return time < action.time;
32-
};
33-
34-
bool operator == (const Action &action) const {
35-
return action.key == key && action.down == down
36-
&& action.time == time;
37-
};
38-
39-
// Only used for debugging
40-
void log() const;
41-
42-
inline void execute() const {
43-
Process::send_scan_code(scan_code, down);
44-
}
45-
};
17+
using HitObject = internal::hit_object;
4618

4719
class Osu : public Process {
4820
// TODO: Generic pointers are bad in the long run.
@@ -58,9 +30,9 @@ namespace osu {
5830

5931
bool is_playing();
6032

61-
internal::map_player get_map_player();
33+
std::vector<HitObject> get_hit_objects();
6234

63-
static std::string get_key_subset(int column_count);
35+
static std::string get_key_subset(const std::string &keys, int column_count);
6436
};
6537

6638
inline int32_t Osu::get_game_time() {

lib/include/maniac/osu/internal.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
inline Process *process;
44

5-
struct hit_object {
5+
class hit_object {
66
uintptr_t base;
77

8+
public:
89
int32_t start_time;
910
int32_t end_time;
1011
int32_t type;
1112
int32_t column;
1213

14+
bool is_slider;
15+
1316
hit_object() : base(0), start_time(0), end_time(0), type(0),
1417
column(0) {}
1518

@@ -18,8 +21,11 @@ struct hit_object {
1821
end_time = get_end_time();
1922
type = get_type();
2023
column = get_column();
24+
25+
is_slider = start_time != end_time;
2126
}
2227

28+
private:
2329
[[nodiscard]] int32_t get_start_time() const {
2430
return process->read_memory<int32_t>(base + 0x10);
2531
}

0 commit comments

Comments
 (0)