1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use anyhow::Context;
use client::GameplaySocket;
use common::{ChunkUpdate, ClientAction, ServerAction, World};
use log::LevelFilter;
use rand::Rng;
use std::thread;
use std::time::{Duration, Instant};
fn main() -> anyhow::Result<()> {
let bot_count = std::env::args()
.nth(1)
.context("No bot count argument provided.\n Usage: bot_client <amount of bots>")?
.parse::<u32>()
.context("Could not parse the given amount of bots as a number.")?;
static LOGGER: client::Logger = client::Logger;
let _ = log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info));
thread::scope(|s| {
for i in 0..bot_count {
s.spawn(move || {
if let Err(err) = bot_main(i) {
log::error!("[bot-{i}]: {err}");
}
});
}
});
Ok(())
}
fn bot_main(bot_i: u32) -> anyhow::Result<()> {
let mut world = World::empty();
let mut latest_verified_world = world.clone();
let mut real_time = 0.0;
let mut gameplay_socket = GameplaySocket::new()?;
let mut pc_handle = None;
let mut connected = false;
gameplay_socket.send(ClientAction::Hello);
let mut rng = rand::thread_rng();
loop {
let start_time = Instant::now();
let mut action_to_send = None;
if let Some((_, pc)) = pc_handle.and_then(|h| world.get_player_character(h)) {
if pc.current_move.is_none() && rng.gen::<f32>() < 0.05 {
match rng.gen_range(0..4) {
3 => action_to_send = Some(ClientAction::MoveRight),
2 => action_to_send = Some(ClientAction::MoveLeft),
1 => action_to_send = Some(ClientAction::MoveDown),
_ => action_to_send = Some(ClientAction::MoveUp),
}
}
}
if let Some(action) = action_to_send {
gameplay_socket.send(action);
}
gameplay_socket.transport();
while let Some(server_action) = gameplay_socket.recv() {
match server_action {
ServerAction::Effects {
time,
chunk_updates,
} => {
if world.time > time {
world = latest_verified_world;
}
while world.time < time {
world.update();
}
for (i, update) in chunk_updates {
match update {
ChunkUpdate::Effects(effects) => {
if !world.apply_effects(i, effects) {
log::warn!(
"[bot-{bot_i}]: Got effects for chunk at index {i}, which is not loaded!"
);
}
}
ChunkUpdate::ResetTo(chunk) => {
world.chunks[i] = Some(chunk);
}
}
}
world.update();
latest_verified_world = world.clone();
real_time = world.time as f32;
}
ServerAction::Welcome(handle) => {
log::info!(
"[bot-{bot_i}]: Up and running with handle: {:016x}",
handle.0
);
pc_handle = Some(handle);
connected = true;
}
ServerAction::UnloadChunks(chunk_indices) => {
for i in chunk_indices {
world.chunks[i] = None;
}
}
}
}
while real_time as u64 > world.time {
world.update();
}
if connected && gameplay_socket.disconnected() {
log::warn!("[bot-{bot_i}]: Disconnected! Exiting.");
break;
}
let frame_time_so_far = Instant::now() - start_time;
let target_frame_time = Duration::from_micros(16_667);
if let Some(sleep_time) = target_frame_time.checked_sub(frame_time_so_far) {
std::thread::sleep(sleep_time);
}
let dt = (Instant::now() - start_time).as_secs_f32();
real_time += dt * common::SECOND as f32;
}
Ok(())
}