dcso3/
net.rs

1/*
2Copyright 2024 Eric Stokes.
3
4This file is part of dcso3.
5
6dcso3 is free software: you can redistribute it and/or modify it under
7the terms of the MIT License.
8
9dcso3 is distributed in the hope that it will be useful, but WITHOUT
10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11FITNESS FOR A PARTICULAR PURPOSE.
12*/
13
14use super::{as_tbl, coalition::Side, cvt_err, String};
15use crate::{
16    env::miz::UnitId, err, lua_err, simple_enum, wrapped_prim, wrapped_table, LuaEnv, Sequence,
17};
18use anyhow::{bail, Result};
19use compact_str::format_compact;
20use core::fmt;
21use fixedstr::str48;
22use mlua::{prelude::*, Value};
23use serde_derive::{Deserialize, Serialize};
24use std::{ops::Deref, str::FromStr};
25
26simple_enum!(PlayerStat, u8, [
27    Car => 2,
28    Crash => 1,
29    Eject => 7,
30    ExtraAllyAAA => 17,
31    ExtraAllyFighters => 14,
32    ExtraAllySAM => 16,
33    ExtraAllyTransports => 15,
34    ExtraAllyTroops => 18,
35    ExtraAllyCoalition => 19,
36    ExtraEnemyAAA => 12,
37    ExtraEnemyFighters => 9,
38    ExtraEnemySAM => 11,
39    ExtraEnemyTransports => 10,
40    ExtraEnemyTroops => 13,
41    Land => 6,
42    Num => 20,
43    OldScore => 8,
44    Ping => 0,
45    Plane => 3,
46    Score => 5,
47    Ship => 4
48]);
49
50wrapped_prim!(PlayerId, i64, Copy, Hash);
51
52impl FromStr for PlayerId {
53    type Err = anyhow::Error;
54
55    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
56        Ok(Self(s.parse::<i64>()?))
57    }
58}
59
60impl fmt::Display for PlayerId {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "{}", self.0)
63    }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
67#[serde(into = "crate::String")]
68#[serde(try_from = "crate::String")]
69pub enum SlotId {
70    Unit(i64),
71    MultiCrew(i64, u8),
72    Spectator,
73    ArtilleryCommander(Side, u8),
74    ForwardObserver(Side, u8),
75    Observer(Side, u8),
76    Instructor(Side, u8),
77}
78
79impl<'lua> FromLua<'lua> for SlotId {
80    fn from_lua(value: Value<'lua>, _: &'lua Lua) -> LuaResult<Self> {
81        match value {
82            Value::Integer(i) => {
83                if i == 0 {
84                    Ok(Self::Spectator)
85                } else {
86                    Ok(Self::Unit(i))
87                }
88            }
89            Value::Number(i) => {
90                let i = i as i64;
91                if i == 0 {
92                    Ok(Self::Spectator)
93                } else {
94                    Ok(Self::Unit(i))
95                }
96            }
97            Value::String(s) => Self::parse_string_slot(s.to_str().map_err(lua_err)?.trim()),
98            v => Err(lua_err(format!("invalid slot type {:?}", v))),
99        }
100    }
101}
102
103impl fmt::Display for SlotId {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "{}", Into::<String>::into(*self))
106    }
107}
108
109impl<'lua> IntoLua<'lua> for SlotId {
110    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
111        match self {
112            Self::Unit(i) => {
113                if i < 1 {
114                    return Err(lua_err("invalid unit number"));
115                }
116                Ok(Value::Integer(i))
117            }
118            Self::Spectator => Ok(Value::String(lua.create_string("")?)),
119            Self::ArtilleryCommander(s, n) => {
120                if n < 1 {
121                    return Err(lua_err("invalid ca slot number"));
122                }
123                String(format_compact!("artillery_commander_{s}_{n}")).into_lua(lua)
124            }
125            Self::ForwardObserver(s, n) => {
126                if n < 1 {
127                    return Err(lua_err("invalid ca slot number"));
128                }
129                String(format_compact!("forward_observer_{s}_{n}")).into_lua(lua)
130            }
131            Self::Instructor(s, n) => {
132                if n < 1 {
133                    return Err(lua_err("invalid ca slot number"));
134                }
135                String(format_compact!("instructor_{s}_{n}")).into_lua(lua)
136            }
137            Self::Observer(s, n) => {
138                if n < 1 {
139                    return Err(lua_err("invalid ca slot number"));
140                }
141                String(format_compact!("observer_{s}_{n}")).into_lua(lua)
142            }
143            Self::MultiCrew(i, n) => {
144                if n < 1 {
145                    return Err(lua_err("invalid multi crew slot number"));
146                }
147                String(format_compact!("{i}_{n}")).into_lua(lua)
148            }
149        }
150    }
151}
152
153impl TryFrom<String> for SlotId {
154    type Error = anyhow::Error;
155
156    fn try_from(s: String) -> Result<Self> {
157        match s.parse::<i64>() {
158            Ok(i) => {
159                if i == 0 {
160                    Ok(Self::Spectator)
161                } else {
162                    Ok(Self::Unit(i))
163                }
164            }
165            Err(_) => Ok(Self::parse_string_slot(s.as_str())?),
166        }
167    }
168}
169
170impl Into<String> for SlotId {
171    fn into(self) -> String {
172        String::from(match self {
173            Self::Unit(i) => format_compact!("{i}"),
174            Self::Spectator => format_compact!("0"),
175            Self::ArtilleryCommander(s, n) => format_compact!("artillery_commander_{s}_{n}"),
176            Self::ForwardObserver(s, n) => format_compact!("forward_observer_{s}_{n}"),
177            Self::Instructor(s, n) => format_compact!("instructor_{s}_{n}"),
178            Self::Observer(s, n) => format_compact!("observer_{s}_{n}"),
179            Self::MultiCrew(i, n) => format_compact!("{i}_{n}"),
180        })
181    }
182}
183
184impl From<UnitId> for SlotId {
185    fn from(value: UnitId) -> Self {
186        Self::Unit(value.inner())
187    }
188}
189
190impl SlotId {
191    fn parse_string_slot(s: &str) -> LuaResult<SlotId> {
192        fn side_and_num(s: &str) -> LuaResult<(Side, u8)> {
193            match s.split_once("_") {
194                None => Err(lua_err(format_compact!("side number {s}"))),
195                Some((s, n)) => {
196                    let side = match s {
197                        "red" => Ok(Side::Red),
198                        "blue" => Ok(Side::Blue),
199                        "neutrals" => Ok(Side::Neutral),
200                        s => Err(lua_err(format_compact!("slot side {s}"))),
201                    }?;
202                    let n = n.parse::<u8>().map_err(lua_err)?;
203                    Ok((side, n))
204                }
205            }
206        }
207        if s == "" {
208            Ok(Self::Spectator)
209        } else if let Ok(i) = s.parse::<i64>() {
210            if i == 0 {
211                Ok(Self::Spectator)
212            } else {
213                Ok(Self::Unit(i))
214            }
215        } else if let Some(s) = s.strip_prefix("artillery_commander_") {
216            let (side, n) = side_and_num(s)?;
217            Ok(Self::ArtilleryCommander(side, n))
218        } else if let Some(s) = s.strip_prefix("observer_") {
219            let (side, n) = side_and_num(s)?;
220            Ok(Self::Observer(side, n))
221        } else if let Some(s) = s.strip_prefix("forward_observer_") {
222            let (side, n) = side_and_num(s)?;
223            Ok(Self::ForwardObserver(side, n))
224        } else if let Some(s) = s.strip_prefix("instructor_") {
225            let (side, n) = side_and_num(s)?;
226            Ok(Self::Instructor(side, n))
227        } else {
228            match s.split_once("_") {
229                None => Err(lua_err(format!("invalid string slot {s}"))),
230                Some((i, n)) => {
231                    let i = i.parse::<i64>().map_err(lua_err)?;
232                    let n = n.parse::<u8>().map_err(lua_err)?;
233                    Ok(Self::MultiCrew(i, n))
234                }
235            }
236        }
237    }
238
239    pub fn is_artillery_commander(&self) -> bool {
240        match self {
241            Self::ArtilleryCommander(_, _) => true,
242            _ => false,
243        }
244    }
245
246    pub fn is_observer(&self) -> bool {
247        match self {
248            Self::Observer(_, _) => true,
249            _ => false,
250        }
251    }
252
253    pub fn is_forward_observer(&self) -> bool {
254        match self {
255            Self::ForwardObserver(_, _) => true,
256            _ => false,
257        }
258    }
259
260    pub fn is_instructor(&self) -> bool {
261        match self {
262            Self::Instructor(_, _) => true,
263            _ => false,
264        }
265    }
266
267    pub fn is_spectator(&self) -> bool {
268        match self {
269            Self::Spectator => true,
270            _ => false,
271        }
272    }
273
274    pub fn is_multicrew(&self) -> bool {
275        match self {
276            Self::MultiCrew(_, _) => true,
277            _ => false,
278        }
279    }
280
281    pub fn as_unit_id(&self) -> Option<UnitId> {
282        match self {
283            Self::Unit(i) => Some(UnitId::from(*i)),
284            Self::MultiCrew(i, _) => Some(UnitId::from(*i)),
285            Self::ArtilleryCommander(_, _)
286            | Self::ForwardObserver(_, _)
287            | Self::Instructor(_, _)
288            | Self::Observer(_, _)
289            | Self::Spectator => None,
290        }
291    }
292}
293
294#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
295#[serde(try_from = "str48", into = "str48")]
296pub struct Ucid([u8; 16]);
297
298impl Default for Ucid {
299    fn default() -> Self {
300        Ucid([0; 16])
301    }
302}
303
304impl fmt::Display for Ucid {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        write!(f, "{}", Into::<str48>::into(*self))
307    }
308}
309
310impl fmt::Debug for Ucid {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        write!(f, "{self}")
313    }
314}
315
316impl TryFrom<str48> for Ucid {
317    type Error = anyhow::Error;
318
319    fn try_from(s: str48) -> Result<Self> {
320        s.parse()
321    }
322}
323
324impl FromStr for Ucid {
325    type Err = anyhow::Error;
326
327    fn from_str(s: &str) -> Result<Self> {
328        if s.len() != 32 {
329            bail!("expected a 32 character string got \"{s}\"")
330        }
331        let mut a = [0; 16];
332        for i in 0..16 {
333            let j = i << 1;
334            a[i] = u8::from_str_radix(&s[j..j + 2], 16)?;
335        }
336        Ok(Self(a))
337    }
338}
339
340impl Into<str48> for Ucid {
341    fn into(self) -> str48 {
342        use std::fmt::Write;
343        let mut s = str48::new();
344        for i in 0..16 {
345            write!(s, "{:02x}", self.0[i]).unwrap()
346        }
347        s
348    }
349}
350
351impl<'lua> FromLua<'lua> for Ucid {
352    fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> LuaResult<Self> {
353        match value {
354            Value::String(s) => s
355                .to_str()?
356                .parse()
357                .map_err(|e| err(&format_compact!("decoding ucid {}", e))),
358            _ => Err(err("expected ucid to be a string")),
359        }
360    }
361}
362
363impl<'lua> IntoLua<'lua> for Ucid {
364    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
365        let s: str48 = self.into();
366        Ok(Value::String(lua.create_string(s.as_str())?))
367    }
368}
369
370wrapped_table!(PlayerInfo, None);
371
372impl<'lua> PlayerInfo<'lua> {
373    pub fn id(&self) -> Result<PlayerId> {
374        Ok(self.t.raw_get("id")?)
375    }
376
377    pub fn name(&self) -> Result<String> {
378        Ok(self.t.raw_get("name")?)
379    }
380
381    pub fn side(&self) -> Result<Side> {
382        Ok(self.t.raw_get("side")?)
383    }
384
385    pub fn slot(&self) -> Result<SlotId> {
386        Ok(self.t.raw_get("slot")?)
387    }
388
389    pub fn ping(&self) -> Result<f32> {
390        Ok(self.t.raw_get("ping")?)
391    }
392
393    pub fn ip(&self) -> Result<Option<String>> {
394        Ok(self.t.raw_get("ipaddr")?)
395    }
396
397    pub fn ucid(&self) -> Result<Option<Ucid>> {
398        Ok(self.t.raw_get("ucid")?)
399    }
400}
401
402#[derive(Debug, Clone, Copy)]
403pub enum DcsLuaEnvironment {
404    /// aka hooks
405    Server,
406    Mission,
407    Config,
408    Export,
409}
410
411impl<'lua> IntoLua<'lua> for DcsLuaEnvironment {
412    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
413        Ok(Value::String(match self {
414            Self::Server => lua.create_string("server"),
415            Self::Mission => lua.create_string("mission"),
416            Self::Config => lua.create_string("config"),
417            Self::Export => lua.create_string("export"),
418        }?))
419    }
420}
421
422wrapped_table!(Net, None);
423
424impl<'lua> Net<'lua> {
425    pub fn singleton<L: LuaEnv<'lua>>(lua: L) -> Result<Self> {
426        Ok(lua.inner().globals().raw_get("net")?)
427    }
428
429    pub fn send_chat(&self, message: String, all: bool) -> Result<()> {
430        Ok(self.t.call_function("send_chat", (message, all))?)
431    }
432
433    pub fn send_chat_to(
434        &self,
435        message: String,
436        player: PlayerId,
437        from_id: Option<PlayerId>,
438    ) -> Result<()> {
439        Ok(self
440            .t
441            .call_function("send_chat_to", (message, player, from_id))?)
442    }
443
444    pub fn get_player_list(&self) -> Result<Sequence<'lua, PlayerId>> {
445        Ok(self.t.call_function("get_player_list", ())?)
446    }
447
448    pub fn get_my_player_id(&self) -> Result<PlayerId> {
449        Ok(self.t.call_function("get_my_player_id", ())?)
450    }
451
452    pub fn get_server_id(&self) -> Result<PlayerId> {
453        Ok(self.t.call_function("get_server_id", ())?)
454    }
455
456    pub fn get_player_info(&self, id: PlayerId) -> Result<PlayerInfo> {
457        Ok(self.t.call_function("get_player_info", id)?)
458    }
459
460    pub fn kick(&self, id: PlayerId, message: String) -> Result<()> {
461        Ok(self.t.call_function("kick", (id, message))?)
462    }
463
464    pub fn get_stat(&self, id: PlayerId, stat: PlayerStat) -> Result<i64> {
465        Ok(self.t.call_function("get_stat", (id, stat))?)
466    }
467
468    pub fn get_name(&self, id: PlayerId) -> Result<String> {
469        Ok(self.t.call_function("get_name", id)?)
470    }
471
472    pub fn get_slot(&self, id: PlayerId) -> Result<(Side, SlotId)> {
473        Ok(self.t.call_function("get_slot", id)?)
474    }
475
476    pub fn force_player_slot(&self, id: PlayerId, side: Side, slot: SlotId) -> Result<()> {
477        Ok(self
478            .t
479            .call_function("force_player_slot", (id, side, slot))?)
480    }
481
482    pub fn lua2json<T: IntoLua<'lua>>(&self, v: T) -> Result<String> {
483        Ok(self.t.call_function("lua2json", v)?)
484    }
485
486    pub fn json2lua<T: FromLua<'lua>>(&self, v: String) -> Result<T> {
487        Ok(self.t.call_function("json2lua", v)?)
488    }
489
490    pub fn dostring_in(&self, state: DcsLuaEnvironment, dostring: String) -> Result<String> {
491        Ok(self.t.call_function("dostring_in", (state, dostring))?)
492    }
493
494    pub fn log(&self, message: String) -> Result<()> {
495        Ok(self.t.call_function("log", message)?)
496    }
497}