1 use crate::{GfxEvent, NetEvent};
2 use cgmath::{Deg, Point3, Vector3};
3 use futures::future::OptionFuture;
4 use mt_net::{CltSender, ReceiverExt, SenderExt, ToCltPkt, ToSrvPkt};
7 use srp::{client::SrpClient, groups::G_2048};
8 use std::{future::Future, time::Duration};
11 time::{interval, Instant, Interval},
13 use winit::event_loop::EventLoopProxy;
17 Verify(Vec<u8>, SrpClient<'static, Sha256>),
24 send_pos_iv: Option<Interval>,
30 events: EventLoopProxy<GfxEvent>,
33 fn maybe_tick(iv: Option<&mut Interval>) -> OptionFuture<impl Future<Output = Instant> + '_> {
34 OptionFuture::from(iv.map(Interval::tick))
37 pub(crate) async fn run(
38 evt_out: EventLoopProxy<GfxEvent>,
39 mut evt_in: mpsc::UnboundedReceiver<NetEvent>,
41 let (tx, mut rx, worker) = mt_net::connect("localhost:30000").await.unwrap();
45 auth: AuthState::Init(interval(Duration::from_millis(100))),
47 username: "shrek".into(), // shrek is love, shrek is life <3
48 password: "boobies".into(),
49 pos: Point3::new(0.0, 0.0, 0.0),
55 let init_pkt = ToSrvPkt::Init {
56 serialize_version: 29,
57 proto_version: 40..=40,
58 player_name: conn.username.clone(),
59 send_full_item_meta: false,
62 let worker_thread = tokio::spawn(worker.run());
66 pkt = rx.recv() => match pkt {
68 Some(Err(e)) => eprintln!("{e}"),
69 Some(Ok(v)) => conn.handle_pkt(v).await,
71 Some(_) = maybe_tick(match &mut conn.auth {
72 AuthState::Init(iv) => Some(iv),
75 conn.tx.send(&init_pkt).await.unwrap();
77 Some(_) = maybe_tick(conn.send_pos_iv.as_mut()) => {
79 .send(&ToSrvPkt::PlayerPos(mt_net::PlayerPos {
81 vel: Vector3::new(0.0, 0.0, 0.0),
84 keys: mt_net::enumset::EnumSet::empty(),
85 fov: Deg(90.0).into(),
91 evt = evt_in.recv() => {
93 Some(NetEvent::PlayerPos(pos, yaw, pitch)) => {
98 Some(NetEvent::Ready) => {
100 .send(&ToSrvPkt::CltReady {
105 version: format!("Minetest Rust {}", env!("CARGO_PKG_VERSION")),
111 None => conn.tx.close(),
114 _ = tokio::signal::ctrl_c() => {
120 worker_thread.await.unwrap();
124 async fn handle_pkt(&mut self, pkt: ToCltPkt) {
133 use mt_net::AuthMethod;
135 if !matches!(self.auth, AuthState::Init(_)) {
139 let srp = SrpClient::<Sha256>::new(&G_2048);
141 let mut rand_bytes = vec![0; 32];
142 rand::thread_rng().fill_bytes(&mut rand_bytes);
144 if self.username != name {
145 panic!("username changed");
148 if auth_methods.contains(AuthMethod::FirstSrp) {
149 let verifier = srp.compute_verifier(
150 self.username.to_lowercase().as_bytes(),
151 self.password.as_bytes(),
156 .send(&ToSrvPkt::FirstSrp {
159 empty_passwd: self.password.is_empty(),
164 self.auth = AuthState::Done;
165 } else if auth_methods.contains(AuthMethod::Srp) {
166 let a = srp.compute_public_ephemeral(&rand_bytes);
169 .send(&ToSrvPkt::SrpBytesA { a, no_sha1: true })
173 self.auth = AuthState::Verify(rand_bytes, srp);
175 panic!("unsupported auth methods: {auth_methods:?}");
178 SrpBytesSaltB { salt, b } => {
179 if let AuthState::Verify(a, srp) = &self.auth {
183 self.username.to_lowercase().as_bytes(),
184 self.password.as_bytes(),
192 self.tx.send(&ToSrvPkt::SrpBytesM { m }).await.unwrap();
194 self.auth = AuthState::Done;
198 self.events.send_event(GfxEvent::NodeDefs(defs.0)).ok();
201 println!("kicked: {reason}");
203 AcceptAuth { player_pos, .. } => {
205 .send(&ToSrvPkt::Init2 {
206 lang: "en_US".into(), // localization is unironically overrated
211 self.pos = player_pos;
212 self.send_pos_iv = Some(interval(Duration::from_millis(100)));
214 MovePlayer { pos, pitch, yaw } => {
220 .send_event(GfxEvent::PlayerPos(self.pos, self.pitch, self.yaw))
223 BlockData { pos, block } => {
224 self.events.send_event(GfxEvent::MapBlock(pos, block)).ok();
226 .send(&ToSrvPkt::GotBlocks {
227 blocks: Vec::from([pos]),
232 AnnounceMedia { files, .. } => {
234 .send(&ToSrvPkt::RequestMedia {
235 filenames: files.into_keys().collect(), // TODO: cache
240 Media { files, n, i } => {
242 .send_event(GfxEvent::Media(files, i + 1 == n))
245 ChatMsg { text, .. } => {