Skip to main content

twgame_core/
net_msg.rs

1pub use libtw2_gamenet_ddnet::msg::game::Game as DdnetGameMsg;
2pub use libtw2_gamenet_teeworlds_0_7::msg::game::Game as Tw07GameMsg;
3use libtw2_gamenet_teeworlds_0_7::msg::Game;
4use libtw2_packer::{with_packer, Unpacker};
5use libtw2_warn::Warn;
6use std::fmt;
7
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub enum NetVersion {
10    V06,
11    V07,
12    Unknown,
13}
14
15pub enum Error<'a> {
16    NonClientGameMsg06(DdnetGameMsg<'a>),
17    NonClientGameMsg07(Tw07GameMsg<'a>),
18    NetMsgParseError(libtw2_gamenet_ddnet::error::Error),
19}
20
21impl fmt::Display for Error<'_> {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        use Error::*;
24        match self {
25            NonClientGameMsg06(msg) => write!(f, "NonClientGameMsg06 {msg:?}"),
26            NonClientGameMsg07(msg) => write!(f, "NonClientGameMsg07 {msg:?}"),
27            NetMsgParseError(err) => write!(f, "NetMsgParseError {err:?}"),
28        }
29    }
30}
31
32#[derive(Debug)]
33pub enum Chat {
34    None,
35    All,
36    Team,
37    Whisper,
38}
39
40impl From<libtw2_gamenet_teeworlds_0_7::enums::Chat> for Chat {
41    fn from(chat: libtw2_gamenet_teeworlds_0_7::enums::Chat) -> Chat {
42        use Chat::*;
43        match chat {
44            libtw2_gamenet_teeworlds_0_7::enums::Chat::None => None,
45            libtw2_gamenet_teeworlds_0_7::enums::Chat::All => All,
46            libtw2_gamenet_teeworlds_0_7::enums::Chat::Team => Team,
47            libtw2_gamenet_teeworlds_0_7::enums::Chat::Whisper => Whisper,
48        }
49    }
50}
51
52pub struct ClSay<'a> {
53    pub mode: Chat,
54    pub target: i32,
55    pub message: &'a [u8],
56}
57
58impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClSay<'a>> for ClSay<'a> {
59    fn from(cl_say: libtw2_gamenet_ddnet::msg::game::ClSay<'a>) -> ClSay<'a> {
60        ClSay {
61            mode: if cl_say.team { Chat::Team } else { Chat::All },
62            target: -1,
63            message: cl_say.message,
64        }
65    }
66}
67
68impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClSay<'a>> for ClSay<'a> {
69    fn from(cl_say: libtw2_gamenet_teeworlds_0_7::msg::game::ClSay<'a>) -> ClSay<'a> {
70        ClSay {
71            mode: cl_say.mode.into(),
72            target: cl_say.target,
73            message: cl_say.message,
74        }
75    }
76}
77
78#[derive(Debug)]
79pub enum Team {
80    Spectators = -1,
81    Red,
82    Blue,
83}
84
85impl From<libtw2_gamenet_ddnet::enums::Team> for Team {
86    fn from(team: libtw2_gamenet_ddnet::enums::Team) -> Team {
87        use Team::*;
88        match team {
89            libtw2_gamenet_ddnet::enums::Team::Spectators => Spectators,
90            libtw2_gamenet_ddnet::enums::Team::Red => Red,
91            libtw2_gamenet_ddnet::enums::Team::Blue => Blue,
92            libtw2_gamenet_ddnet::enums::Team::All => Spectators, // TODO
93            libtw2_gamenet_ddnet::enums::Team::WhisperSend => Spectators, // TODO
94            libtw2_gamenet_ddnet::enums::Team::WhisperRecv => Spectators, // TODO
95        }
96    }
97}
98
99impl From<libtw2_gamenet_teeworlds_0_7::enums::Team> for Team {
100    fn from(team: libtw2_gamenet_teeworlds_0_7::enums::Team) -> Team {
101        use Team::*;
102        match team {
103            libtw2_gamenet_teeworlds_0_7::enums::Team::Spectators => Spectators,
104            libtw2_gamenet_teeworlds_0_7::enums::Team::Red => Red,
105            libtw2_gamenet_teeworlds_0_7::enums::Team::Blue => Blue,
106        }
107    }
108}
109
110#[derive(Debug)]
111pub enum Spec {
112    Freeview,
113    Player,
114    Flagred,
115    Flagblue,
116}
117
118impl From<libtw2_gamenet_teeworlds_0_7::enums::Spec> for Spec {
119    fn from(spec: libtw2_gamenet_teeworlds_0_7::enums::Spec) -> Spec {
120        use Spec::*;
121        match spec {
122            libtw2_gamenet_teeworlds_0_7::enums::Spec::Freeview => Freeview,
123            libtw2_gamenet_teeworlds_0_7::enums::Spec::Player => Player,
124            libtw2_gamenet_teeworlds_0_7::enums::Spec::Flagred => Flagred,
125            libtw2_gamenet_teeworlds_0_7::enums::Spec::Flagblue => Flagblue,
126        }
127    }
128}
129
130pub struct ClSetSpectatorMode {
131    pub spec_mode: Spec,
132    pub spectator_id: i32,
133}
134
135impl From<libtw2_gamenet_ddnet::msg::game::ClSetSpectatorMode> for ClSetSpectatorMode {
136    fn from(
137        cl_set_spectator_mode: libtw2_gamenet_ddnet::msg::game::ClSetSpectatorMode,
138    ) -> ClSetSpectatorMode {
139        ClSetSpectatorMode {
140            spec_mode: if cl_set_spectator_mode.spectator_id == -1 {
141                Spec::Freeview
142            } else {
143                Spec::Player
144            },
145            spectator_id: cl_set_spectator_mode.spectator_id,
146        }
147    }
148}
149
150impl From<libtw2_gamenet_teeworlds_0_7::msg::game::ClSetSpectatorMode> for ClSetSpectatorMode {
151    fn from(
152        cl_set_spectator_mode: libtw2_gamenet_teeworlds_0_7::msg::game::ClSetSpectatorMode,
153    ) -> ClSetSpectatorMode {
154        ClSetSpectatorMode {
155            spec_mode: cl_set_spectator_mode.spec_mode.into(),
156            spectator_id: cl_set_spectator_mode.spectator_id,
157        }
158    }
159}
160
161#[repr(i32)]
162#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)]
163pub enum Emoticon {
164    Oop,
165    Exclamation,
166    Hearts,
167    Drop,
168    Dotdot,
169    Music,
170    Sorry,
171    Ghost,
172    Sushi,
173    Splattee,
174    Deviltee,
175    Zomg,
176    Zzz,
177    Wtf,
178    Eyes,
179    Question,
180}
181
182impl From<libtw2_gamenet_ddnet::enums::Emoticon> for Emoticon {
183    fn from(emoticon: libtw2_gamenet_ddnet::enums::Emoticon) -> Emoticon {
184        use Emoticon::*;
185        match emoticon {
186            libtw2_gamenet_ddnet::enums::Emoticon::Oop => Oop,
187            libtw2_gamenet_ddnet::enums::Emoticon::Exclamation => Exclamation,
188            libtw2_gamenet_ddnet::enums::Emoticon::Hearts => Hearts,
189            libtw2_gamenet_ddnet::enums::Emoticon::Drop => Drop,
190            libtw2_gamenet_ddnet::enums::Emoticon::Dotdot => Dotdot,
191            libtw2_gamenet_ddnet::enums::Emoticon::Music => Music,
192            libtw2_gamenet_ddnet::enums::Emoticon::Sorry => Sorry,
193            libtw2_gamenet_ddnet::enums::Emoticon::Ghost => Ghost,
194            libtw2_gamenet_ddnet::enums::Emoticon::Sushi => Sushi,
195            libtw2_gamenet_ddnet::enums::Emoticon::Splattee => Splattee,
196            libtw2_gamenet_ddnet::enums::Emoticon::Deviltee => Deviltee,
197            libtw2_gamenet_ddnet::enums::Emoticon::Zomg => Zomg,
198            libtw2_gamenet_ddnet::enums::Emoticon::Zzz => Zzz,
199            libtw2_gamenet_ddnet::enums::Emoticon::Wtf => Wtf,
200            libtw2_gamenet_ddnet::enums::Emoticon::Eyes => Eyes,
201            libtw2_gamenet_ddnet::enums::Emoticon::Question => Question,
202        }
203    }
204}
205
206impl From<libtw2_gamenet_teeworlds_0_7::enums::Emoticon> for Emoticon {
207    fn from(emoticon: libtw2_gamenet_teeworlds_0_7::enums::Emoticon) -> Emoticon {
208        use Emoticon::*;
209        match emoticon {
210            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Oop => Oop,
211            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Exclamation => Exclamation,
212            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Hearts => Hearts,
213            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Drop => Drop,
214            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Dotdot => Dotdot,
215            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Music => Music,
216            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Sorry => Sorry,
217            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Ghost => Ghost,
218            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Sushi => Sushi,
219            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Splattee => Splattee,
220            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Deviltee => Deviltee,
221            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Zomg => Zomg,
222            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Zzz => Zzz,
223            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Wtf => Wtf,
224            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Eyes => Eyes,
225            libtw2_gamenet_teeworlds_0_7::enums::Emoticon::Question => Question,
226        }
227    }
228}
229
230pub struct ClCallVote<'a> {
231    pub type_: &'a [u8],
232    pub value: &'a [u8],
233    pub reason: &'a [u8],
234    pub force: bool,
235}
236
237impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClCallVote<'a>> for ClCallVote<'a> {
238    fn from(cl_call_vote: libtw2_gamenet_ddnet::msg::game::ClCallVote<'a>) -> ClCallVote<'a> {
239        ClCallVote {
240            type_: cl_call_vote.type_,
241            value: cl_call_vote.value,
242            reason: cl_call_vote.reason,
243            force: false,
244        }
245    }
246}
247
248impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClCallVote<'a>> for ClCallVote<'a> {
249    fn from(
250        cl_call_vote: libtw2_gamenet_teeworlds_0_7::msg::game::ClCallVote<'a>,
251    ) -> ClCallVote<'a> {
252        ClCallVote {
253            type_: cl_call_vote.type_,
254            value: cl_call_vote.value,
255            reason: cl_call_vote.reason,
256            force: cl_call_vote.force,
257        }
258    }
259}
260
261pub struct Skin06<'a> {
262    pub skin: &'a [u8],
263    pub use_custom_color: bool,
264    pub color_body: i32,
265    pub color_feet: i32,
266}
267
268pub struct Skin07<'a> {
269    pub skin_part_names: [&'a [u8]; 6],
270    pub use_custom_colors: [bool; 6],
271    pub skin_part_colors: [i32; 6],
272}
273
274pub enum Skin<'a> {
275    V6(Skin06<'a>),
276    V7(Skin07<'a>),
277}
278
279impl fmt::Display for Skin<'_> {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        match self {
282            Skin::V6(v6) => {
283                let Skin06 {
284                    skin,
285                    use_custom_color,
286                    color_body,
287                    color_feet,
288                } = v6;
289                let skin = String::from_utf8_lossy(skin);
290                write!(f, "{{ skin={skin:?} use_custom_color={use_custom_color} color_body={color_body} color_feet={color_feet} }}")
291            }
292            Skin::V7(v7) => {
293                let Skin07 {
294                    skin_part_names,
295                    use_custom_colors,
296                    skin_part_colors,
297                } = v7;
298                write!(f, "{{")?;
299                for i in 0..6 {
300                    let name = String::from_utf8_lossy(skin_part_names[i]);
301                    let custom = use_custom_colors[i];
302                    let color = skin_part_colors[i];
303                    write!(f, "(part={name} use_custom_color={custom} color={color})")?;
304                    if i != 5 {
305                        write!(f, " ")?;
306                    }
307                }
308                write!(f, "}}")
309            }
310        }
311    }
312}
313
314pub struct ClPlayerInfo<'a> {
315    pub name: &'a [u8],
316    pub clan: &'a [u8],
317    pub country: i32,
318    pub skin: Skin<'a>,
319}
320
321impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClStartInfo<'a>> for ClPlayerInfo<'a> {
322    fn from(cl_start_info: libtw2_gamenet_ddnet::msg::game::ClStartInfo<'a>) -> ClPlayerInfo<'a> {
323        ClPlayerInfo {
324            name: cl_start_info.name,
325            clan: cl_start_info.clan,
326            country: cl_start_info.country,
327            skin: Skin::V6(Skin06 {
328                skin: cl_start_info.skin,
329                use_custom_color: cl_start_info.use_custom_color,
330                color_body: cl_start_info.color_body,
331                color_feet: cl_start_info.color_feet,
332            }),
333        }
334    }
335}
336
337impl<'a> From<libtw2_gamenet_ddnet::msg::game::ClChangeInfo<'a>> for ClPlayerInfo<'a> {
338    fn from(cl_change_info: libtw2_gamenet_ddnet::msg::game::ClChangeInfo<'a>) -> ClPlayerInfo<'a> {
339        ClPlayerInfo {
340            name: cl_change_info.name,
341            clan: cl_change_info.clan,
342            country: cl_change_info.country,
343            skin: Skin::V6(Skin06 {
344                skin: cl_change_info.skin,
345                use_custom_color: cl_change_info.use_custom_color,
346                color_body: cl_change_info.color_body,
347                color_feet: cl_change_info.color_feet,
348            }),
349        }
350    }
351}
352
353impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClStartInfo<'a>> for ClPlayerInfo<'a> {
354    fn from(
355        cl_start_info: libtw2_gamenet_teeworlds_0_7::msg::game::ClStartInfo<'a>,
356    ) -> ClPlayerInfo<'a> {
357        ClPlayerInfo {
358            name: cl_start_info.name,
359            clan: cl_start_info.clan,
360            country: cl_start_info.country,
361            skin: Skin::V7(Skin07 {
362                skin_part_names: cl_start_info.skin_part_names,
363                use_custom_colors: cl_start_info.use_custom_colors,
364                skin_part_colors: cl_start_info.skin_part_colors,
365            }),
366        }
367    }
368}
369
370pub struct ClShowDistance {
371    pub x: i32,
372    pub y: i32,
373}
374
375impl From<libtw2_gamenet_ddnet::msg::game::ClShowDistance> for ClShowDistance {
376    fn from(cl_show_distance: libtw2_gamenet_ddnet::msg::game::ClShowDistance) -> ClShowDistance {
377        ClShowDistance {
378            x: cl_show_distance.x,
379            y: cl_show_distance.y,
380        }
381    }
382}
383
384pub struct ClCameraInfo {
385    pub zoom: i32,
386    pub deadzone: i32,
387    pub follow_factor: i32,
388}
389
390impl From<libtw2_gamenet_ddnet::msg::game::ClCameraInfo> for ClCameraInfo {
391    fn from(cl_camera_info: libtw2_gamenet_ddnet::msg::game::ClCameraInfo) -> ClCameraInfo {
392        ClCameraInfo {
393            zoom: cl_camera_info.zoom,
394            deadzone: cl_camera_info.deadzone,
395            follow_factor: cl_camera_info.follow_factor,
396        }
397    }
398}
399
400pub struct ClCommand<'a> {
401    pub name: &'a [u8],
402    pub arguments: &'a [u8],
403}
404
405impl<'a> From<libtw2_gamenet_teeworlds_0_7::msg::game::ClCommand<'a>> for ClCommand<'a> {
406    fn from(cl_command: libtw2_gamenet_teeworlds_0_7::msg::game::ClCommand) -> ClCommand {
407        ClCommand {
408            name: cl_command.name,
409            arguments: cl_command.arguments,
410        }
411    }
412}
413
414pub enum ClNetMessage<'a> {
415    ClSay(ClSay<'a>),
416    ClSetTeam(Team),
417    ClSetSpectatorMode(ClSetSpectatorMode),
418    ClStartInfo(ClPlayerInfo<'a>),
419    ClChangeInfo(ClPlayerInfo<'a>),
420    ClKill,
421    ClEmoticon(Emoticon),
422    ClVote(i32),
423    ClCallVote(ClCallVote<'a>),
424    /// contains the ddnet version
425    ClIsDdnet(i32),
426    ClShowOthers(i32),
427    ClShowDistance(ClShowDistance),
428    ClCameraInfo(ClCameraInfo),
429    ClEnableSpectatorCount(bool),
430    ClCommand(ClCommand<'a>),
431}
432
433impl<'a> ClNetMessage<'a> {
434    pub fn serialize_ddnet06(&self) -> Vec<u8> {
435        let ddnet_msg = self.to_ddnet06_msg();
436        const DDNET06_MAX_PACKETSIZE: usize = 1400;
437
438        let mut buf = Vec::with_capacity(DDNET06_MAX_PACKETSIZE);
439        // The call sets the length of `buf` through the `VecBuffer` drop.
440        with_packer(&mut buf, |p| ddnet_msg.encode(p))
441            .expect("ddnet 0.6 netmsg exceeds max packet size");
442        buf
443    }
444
445    fn to_ddnet06_msg(&self) -> DdnetGameMsg<'a> {
446        use libtw2_gamenet_ddnet::{enums as ddnet_enums, msg::game as ddnet_game};
447
448        match self {
449            ClNetMessage::ClSay(say) => {
450                let team = match say.mode {
451                    Chat::All => false,
452                    Chat::Team => true,
453                    _ => false,
454                };
455                ddnet_game::Game::ClSay(ddnet_game::ClSay {
456                    team,
457                    message: say.message,
458                })
459            }
460            ClNetMessage::ClSetTeam(team) => ddnet_game::Game::ClSetTeam(ddnet_game::ClSetTeam {
461                team: match team {
462                    Team::Spectators => ddnet_enums::Team::Spectators,
463                    Team::Red => ddnet_enums::Team::Red,
464                    Team::Blue => ddnet_enums::Team::Blue,
465                },
466            }),
467            ClNetMessage::ClSetSpectatorMode(mode) => {
468                let spectator_id = match mode.spec_mode {
469                    Spec::Freeview => -1,
470                    _ => mode.spectator_id,
471                };
472                ddnet_game::Game::ClSetSpectatorMode(ddnet_game::ClSetSpectatorMode {
473                    spectator_id,
474                })
475            }
476            ClNetMessage::ClStartInfo(info) => {
477                let skin = match info.skin {
478                    Skin::V6(ref skin) => skin,
479                    Skin::V7(_) => &Skin06 {
480                        skin: b"default",
481                        use_custom_color: false,
482                        color_body: 0,
483                        color_feet: 0,
484                    },
485                };
486                ddnet_game::Game::ClStartInfo(ddnet_game::ClStartInfo {
487                    name: info.name,
488                    clan: info.clan,
489                    country: info.country,
490                    skin: skin.skin,
491                    use_custom_color: skin.use_custom_color,
492                    color_body: skin.color_body,
493                    color_feet: skin.color_feet,
494                })
495            }
496            ClNetMessage::ClChangeInfo(info) => {
497                let skin = match info.skin {
498                    Skin::V6(ref skin) => skin,
499                    Skin::V7(_) => &Skin06 {
500                        skin: b"default",
501                        use_custom_color: false,
502                        color_body: 0,
503                        color_feet: 0,
504                    },
505                };
506                ddnet_game::Game::ClChangeInfo(ddnet_game::ClChangeInfo {
507                    name: info.name,
508                    clan: info.clan,
509                    country: info.country,
510                    skin: skin.skin,
511                    use_custom_color: skin.use_custom_color,
512                    color_body: skin.color_body,
513                    color_feet: skin.color_feet,
514                })
515            }
516            ClNetMessage::ClKill => ddnet_game::Game::ClKill(ddnet_game::ClKill),
517            ClNetMessage::ClEmoticon(emoticon) => {
518                ddnet_game::Game::ClEmoticon(ddnet_game::ClEmoticon {
519                    emoticon: match emoticon {
520                        Emoticon::Oop => ddnet_enums::Emoticon::Oop,
521                        Emoticon::Exclamation => ddnet_enums::Emoticon::Exclamation,
522                        Emoticon::Hearts => ddnet_enums::Emoticon::Hearts,
523                        Emoticon::Drop => ddnet_enums::Emoticon::Drop,
524                        Emoticon::Dotdot => ddnet_enums::Emoticon::Dotdot,
525                        Emoticon::Music => ddnet_enums::Emoticon::Music,
526                        Emoticon::Sorry => ddnet_enums::Emoticon::Sorry,
527                        Emoticon::Ghost => ddnet_enums::Emoticon::Ghost,
528                        Emoticon::Sushi => ddnet_enums::Emoticon::Sushi,
529                        Emoticon::Splattee => ddnet_enums::Emoticon::Splattee,
530                        Emoticon::Deviltee => ddnet_enums::Emoticon::Deviltee,
531                        Emoticon::Zomg => ddnet_enums::Emoticon::Zomg,
532                        Emoticon::Zzz => ddnet_enums::Emoticon::Zzz,
533                        Emoticon::Wtf => ddnet_enums::Emoticon::Wtf,
534                        Emoticon::Eyes => ddnet_enums::Emoticon::Eyes,
535                        Emoticon::Question => ddnet_enums::Emoticon::Question,
536                    },
537                })
538            }
539            ClNetMessage::ClVote(vote) => {
540                ddnet_game::Game::ClVote(ddnet_game::ClVote { vote: *vote })
541            }
542            ClNetMessage::ClCallVote(call) => {
543                ddnet_game::Game::ClCallVote(ddnet_game::ClCallVote {
544                    type_: call.type_,
545                    value: call.value,
546                    reason: call.reason,
547                })
548            }
549            ClNetMessage::ClIsDdnet(version) => {
550                ddnet_game::Game::ClIsDdnetLegacy(ddnet_game::ClIsDdnetLegacy {
551                    ddnet_version: *version,
552                })
553            }
554            ClNetMessage::ClShowOthers(show) => {
555                ddnet_game::Game::ClShowOthers(ddnet_game::ClShowOthers { show: *show })
556            }
557            ClNetMessage::ClShowDistance(dist) => {
558                ddnet_game::Game::ClShowDistance(ddnet_game::ClShowDistance {
559                    x: dist.x,
560                    y: dist.y,
561                })
562            }
563            ClNetMessage::ClCameraInfo(info) => {
564                ddnet_game::Game::ClCameraInfo(ddnet_game::ClCameraInfo {
565                    zoom: info.zoom,
566                    deadzone: info.deadzone,
567                    follow_factor: info.follow_factor,
568                })
569            }
570            ClNetMessage::ClEnableSpectatorCount(enable) => {
571                ddnet_game::Game::ClEnableSpectatorCount(ddnet_game::ClEnableSpectatorCount {
572                    enable: *enable,
573                })
574            }
575            // TODO: actually convert. probably not possible, because cant return reference to heap (format!("/{command} <args>"))
576            ClNetMessage::ClCommand(_) => DdnetGameMsg::ClSay(ddnet_game::ClSay {
577                team: false,
578                message: b"dropped 0.7 command",
579            }),
580        }
581    }
582}
583
584// copied from https://github.com/heinrich5991/libtw2/blob/2872de4573e65d1690f1a5f344311df86d554eb4/tools/src/warn_stdout.rs
585pub struct Stdout;
586impl<W: fmt::Debug> Warn<W> for Stdout {
587    fn warn(&mut self, warning: W) {
588        println!("WARN: {warning:?}");
589    }
590}
591
592#[allow(clippy::result_large_err)]
593fn parse_ddnet(buf: &[u8]) -> Result<ClNetMessage<'_>, Error<'_>> {
594    let mut b = Unpacker::new(buf);
595    // TODO: warning as error?
596    match DdnetGameMsg::decode(&mut Stdout, &mut b) {
597        Ok(msg) => match msg {
598            DdnetGameMsg::SvMotd(_)
599            | DdnetGameMsg::SvBroadcast(_)
600            | DdnetGameMsg::SvChat(_)
601            | DdnetGameMsg::SvKillMsg(_)
602            | DdnetGameMsg::SvKillMsgTeam(_)
603            | DdnetGameMsg::SvSoundGlobal(_)
604            | DdnetGameMsg::SvTuneParams(_)
605            | DdnetGameMsg::SvReadyToEnter(_)
606            | DdnetGameMsg::SvWeaponPickup(_)
607            | DdnetGameMsg::SvEmoticon(_)
608            | DdnetGameMsg::SvVoteClearOptions(_)
609            | DdnetGameMsg::SvVoteOptionListAdd(_)
610            | DdnetGameMsg::SvVoteOptionAdd(_)
611            | DdnetGameMsg::SvVoteOptionRemove(_)
612            | DdnetGameMsg::SvVoteSet(_)
613            | DdnetGameMsg::SvVoteStatus(_)
614            | DdnetGameMsg::SvDdraceTime(_)
615            | DdnetGameMsg::SvRecord(_)
616            | DdnetGameMsg::Unused(_)
617            | DdnetGameMsg::Unused2(_)
618            | DdnetGameMsg::SvTeamsState(_)
619            | DdnetGameMsg::SvMyOwnMessage(_)
620            | DdnetGameMsg::SvDdraceTimeLegacy(_)
621            | DdnetGameMsg::SvRecordLegacy(_)
622            | DdnetGameMsg::SvTeamsStateLegacy(_)
623            | DdnetGameMsg::SvYourVote(_)
624            | DdnetGameMsg::SvRaceFinish(_)
625            | DdnetGameMsg::SvCommandInfo(_)
626            | DdnetGameMsg::SvCommandInfoRemove(_)
627            | DdnetGameMsg::SvVoteOptionGroupStart(_)
628            | DdnetGameMsg::SvVoteOptionGroupEnd(_)
629            | DdnetGameMsg::SvCommandInfoGroupStart(_)
630            | DdnetGameMsg::SvCommandInfoGroupEnd(_)
631            | DdnetGameMsg::SvChangeInfoCooldown(_)
632            | DdnetGameMsg::SvMapSoundGlobal(_)
633            | DdnetGameMsg::SvPreInput(_)
634            | DdnetGameMsg::SvSaveCode(_)
635            | DdnetGameMsg::SvServerAlert(_)
636            | DdnetGameMsg::SvModeratorAlert(_) => Err(Error::NonClientGameMsg06(msg)),
637            DdnetGameMsg::ClSay(msg) => Ok(ClNetMessage::ClSay(msg.into())),
638            DdnetGameMsg::ClSetTeam(msg) => Ok(ClNetMessage::ClSetTeam(msg.team.into())),
639            DdnetGameMsg::ClSetSpectatorMode(msg) => {
640                Ok(ClNetMessage::ClSetSpectatorMode(msg.into()))
641            }
642            DdnetGameMsg::ClStartInfo(msg) => Ok(ClNetMessage::ClStartInfo(msg.into())),
643            DdnetGameMsg::ClChangeInfo(msg) => Ok(ClNetMessage::ClChangeInfo(msg.into())),
644            DdnetGameMsg::ClKill(_) => Ok(ClNetMessage::ClKill),
645            DdnetGameMsg::ClEmoticon(msg) => Ok(ClNetMessage::ClEmoticon(msg.emoticon.into())),
646            DdnetGameMsg::ClVote(msg) => Ok(ClNetMessage::ClVote(msg.vote)),
647            DdnetGameMsg::ClCallVote(msg) => Ok(ClNetMessage::ClCallVote(msg.into())),
648            DdnetGameMsg::ClShowOthersLegacy(msg) => {
649                Ok(ClNetMessage::ClShowOthers(msg.show as i32))
650            }
651            DdnetGameMsg::ClShowDistance(msg) => Ok(ClNetMessage::ClShowDistance(msg.into())),
652            DdnetGameMsg::ClShowOthers(msg) => Ok(ClNetMessage::ClShowOthers(msg.show)),
653            DdnetGameMsg::ClIsDdnetLegacy(_msg) => Ok(ClNetMessage::ClIsDdnet(0)),
654            DdnetGameMsg::ClCameraInfo(msg) => Ok(ClNetMessage::ClCameraInfo(msg.into())),
655            DdnetGameMsg::ClEnableSpectatorCount(msg) => {
656                Ok(ClNetMessage::ClEnableSpectatorCount(msg.enable))
657            }
658        },
659        Err(err) => Err(Error::NetMsgParseError(err)),
660    }
661}
662
663#[allow(clippy::result_large_err)]
664fn parse_teeworlds_07(buf: &[u8]) -> Result<ClNetMessage<'_>, Error<'_>> {
665    let mut b = Unpacker::new(buf);
666    // TODO: warning as error?
667    match Tw07GameMsg::decode(&mut Stdout, &mut b) {
668        Ok(msg) => match msg {
669            Game::SvMotd(_)
670            | Game::SvBroadcast(_)
671            | Game::SvChat(_)
672            | Game::SvTeam(_)
673            | Game::SvKillMsg(_)
674            | Game::SvTuneParams(_)
675            | Game::SvExtraProjectile(_)
676            | Game::SvReadyToEnter(_)
677            | Game::SvWeaponPickup(_)
678            | Game::SvEmoticon(_)
679            | Game::SvVoteClearOptions(_)
680            | Game::SvVoteOptionListAdd(_)
681            | Game::SvVoteOptionAdd(_)
682            | Game::SvVoteOptionRemove(_)
683            | Game::SvVoteSet(_)
684            | Game::SvVoteStatus(_)
685            | Game::SvServerSettings(_)
686            | Game::SvClientInfo(_)
687            | Game::SvGameInfo(_)
688            | Game::SvClientDrop(_)
689            | Game::SvGameMsg(_)
690            | Game::SvRaceFinish(_)
691            | Game::SvCheckpoint(_)
692            | Game::SvCommandInfo(_)
693            | Game::SvCommandInfoRemove(_)
694            | Game::SvSkinChange(_) => Err(Error::NonClientGameMsg07(msg)),
695            Game::DeClientEnter(_) => Err(Error::NonClientGameMsg07(msg)), // TODO
696            Game::DeClientLeave(_) => Err(Error::NonClientGameMsg07(msg)), // TODO
697            Game::ClSay(msg) => Ok(ClNetMessage::ClSay(msg.into())),
698            Game::ClSetTeam(msg) => Ok(ClNetMessage::ClSetTeam(msg.team.into())),
699            Game::ClSetSpectatorMode(msg) => Ok(ClNetMessage::ClSetSpectatorMode(msg.into())),
700            Game::ClStartInfo(msg) => Ok(ClNetMessage::ClStartInfo(msg.into())),
701            Game::ClKill(_) => Ok(ClNetMessage::ClKill),
702            Game::ClReadyChange(_) => Err(Error::NonClientGameMsg07(msg)), // TODO,
703            Game::ClEmoticon(msg) => Ok(ClNetMessage::ClEmoticon(msg.emoticon.into())),
704            Game::ClVote(msg) => Ok(ClNetMessage::ClVote(msg.vote)),
705            Game::ClCallVote(msg) => Ok(ClNetMessage::ClCallVote(msg.into())),
706            Game::ClSkinChange(_) => Err(Error::NonClientGameMsg07(msg)), // TODO,
707            Game::ClCommand(msg) => Ok(ClNetMessage::ClCommand(msg.into())),
708        },
709        Err(err) => Err(Error::NetMsgParseError(err)),
710    }
711}
712
713#[allow(clippy::result_large_err)]
714pub fn parse_net_msg<'a>(
715    buf: &'a [u8],
716    net_version: &mut NetVersion,
717) -> Result<ClNetMessage<'a>, Error<'a>> {
718    match *net_version {
719        NetVersion::V06 => parse_ddnet(buf),
720        NetVersion::V07 => parse_teeworlds_07(buf),
721        NetVersion::Unknown => match parse_ddnet(buf) {
722            Ok(msg) => {
723                *net_version = NetVersion::V06;
724                Ok(msg)
725            }
726            Err(err) => {
727                if let Ok(msg) = parse_teeworlds_07(buf) {
728                    *net_version = NetVersion::V07;
729                    Ok(msg)
730                } else {
731                    Err(err)
732                }
733            }
734        },
735    }
736}