1use 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 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}