dcso3/env/
miz.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 crate::{
15    as_tbl, coalition::Side, controller::MissionPoint, country, is_hooks_env, net::SlotId,
16    string_enum, wrapped_prim, wrapped_table, Color, DcsTableExt, LuaEnv, LuaVec2, Path, Quad2,
17    Sequence, String,
18};
19use anyhow::{bail, Result};
20use fxhash::FxHashMap;
21use mlua::{prelude::*, Value};
22use serde_derive::{Deserialize, Serialize};
23use std::{cmp::max, collections::hash_map::Entry, ops::Deref};
24
25wrapped_table!(Weather, None);
26
27wrapped_prim!(UnitId, i64, Hash, Copy);
28
29impl Default for UnitId {
30    fn default() -> Self {
31        UnitId(0)
32    }
33}
34
35impl UnitId {
36    pub fn next(&mut self) {
37        self.0 += 1
38    }
39}
40
41wrapped_prim!(GroupId, i64, Hash, Copy);
42
43impl Default for GroupId {
44    fn default() -> Self {
45        GroupId(0)
46    }
47}
48
49impl GroupId {
50    pub fn next(&mut self) {
51        self.0 += 1
52    }
53}
54
55string_enum!(Skill, u8, [
56    Client => "Client",
57    Excellant => "Excellant",
58    Player => "Player",
59    Average => "Average",
60    Good => "Good",
61    High => "High"
62]);
63
64#[derive(Debug, Clone)]
65pub struct Property {
66    pub key: String,
67    pub value: String
68}
69
70impl<'lua> FromLua<'lua> for Property {
71    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
72        let tbl = LuaTable::from_lua(value, lua)?;
73        Ok(Self {
74            key: tbl.raw_get("key")?,
75            value: tbl.raw_get("value")?
76        })
77    }
78}
79
80#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
81pub enum TriggerZoneTyp {
82    Circle { radius: f64 },
83    Quad(Quad2),
84}
85
86wrapped_prim!(TriggerZoneId, i64, Copy, Hash);
87
88wrapped_table!(TriggerZone, None);
89
90impl<'lua> TriggerZone<'lua> {
91    pub fn name(&self) -> Result<String> {
92        Ok(self.raw_get("name")?)
93    }
94
95    pub fn pos(&self) -> Result<na::base::Vector2<f64>> {
96        Ok(na::base::Vector2::new(
97            self.raw_get("x")?,
98            self.raw_get("y")?,
99        ))
100    }
101
102    pub fn typ(&self) -> Result<TriggerZoneTyp> {
103        Ok(match self.raw_get("type")? {
104            0 => TriggerZoneTyp::Circle {
105                radius: self.raw_get("radius")?,
106            },
107            2 => TriggerZoneTyp::Quad(self.raw_get("verticies")?),
108            n => bail!("unknown trigger zone type {}", n),
109        })
110    }
111
112    pub fn color(&self) -> Result<Color> {
113        Ok(self.raw_get("color")?)
114    }
115
116    pub fn id(&self) -> Result<TriggerZoneId> {
117        Ok(self.raw_get("zoneId")?)
118    }
119
120    pub fn properties(&self) -> Result<Sequence<'lua, Property>> {
121        Ok(self.raw_get("properties")?)
122    }
123}
124
125wrapped_table!(NavPoint, None);
126
127wrapped_table!(Task, None);
128
129wrapped_table!(Route, None);
130
131impl<'lua> Route<'lua> {
132    pub fn points(&self) -> Result<Sequence<'lua, MissionPoint>> {
133        Ok(self.t.raw_get("points")?)
134    }
135
136    pub fn set_points(&self, points: Vec<MissionPoint>) -> Result<()> {
137        Ok(self.t.raw_set("points", points)?)
138    }
139}
140
141wrapped_table!(Unit, None);
142
143impl<'lua> Unit<'lua> {
144    pub fn name(&self) -> Result<String> {
145        Ok(self.raw_get("name")?)
146    }
147
148    pub fn id(&self) -> Result<UnitId> {
149        Ok(self.raw_get("unitId")?)
150    }
151
152    pub fn set_id(&self, id: UnitId) -> Result<()> {
153        Ok(self.raw_set("unitId", id)?)
154    }
155
156    pub fn slot(&self) -> Result<SlotId> {
157        Ok(SlotId::from(self.id()?))
158    }
159
160    pub fn set_name(&self, name: String) -> Result<()> {
161        Ok(self.raw_set("name", name)?)
162    }
163
164    pub fn pos(&self) -> Result<na::base::Vector2<f64>> {
165        Ok(na::base::Vector2::new(
166            self.raw_get("x")?,
167            self.raw_get("y")?,
168        ))
169    }
170
171    pub fn set_pos(&self, pos: na::base::Vector2<f64>) -> Result<()> {
172        self.raw_set("x", pos.x)?;
173        self.raw_set("y", pos.y)?;
174        Ok(())
175    }
176
177    pub fn heading(&self) -> Result<f64> {
178        Ok(self.raw_get("heading")?)
179    }
180
181    pub fn set_heading(&self, h: f64) -> Result<()> {
182        self.raw_set("psi", -h)?;
183        Ok(self.raw_set("heading", h)?)
184    }
185
186    pub fn alt(&self) -> Result<Option<f64>> {
187        Ok(self.raw_get("alt")?)
188    }
189
190    pub fn set_alt(&self, a: f64) -> Result<()> {
191        Ok(self.raw_set("alt", a)?)
192    }
193
194    pub fn typ(&self) -> Result<String> {
195        Ok(self.raw_get("type")?)
196    }
197
198    pub fn skill(&self) -> Result<Skill> {
199        Ok(self.raw_get("skill")?)
200    }
201}
202
203wrapped_table!(Group, None);
204
205impl<'lua> Group<'lua> {
206    pub fn name(&self) -> Result<String> {
207        Ok(self.raw_get("name")?)
208    }
209
210    pub fn set_name(&self, name: String) -> Result<()> {
211        Ok(self.raw_set("name", name)?)
212    }
213
214    pub fn pos(&self) -> Result<na::base::Vector2<f64>> {
215        Ok(na::base::Vector2::new(
216            self.t.raw_get("x")?,
217            self.t.raw_get("y")?,
218        ))
219    }
220
221    pub fn set_pos(&self, pos: na::base::Vector2<f64>) -> Result<()> {
222        self.t.raw_set("x", pos.x)?;
223        self.t.raw_set("y", pos.y)?;
224        Ok(())
225    }
226
227    pub fn frequency(&self) -> Result<f64> {
228        Ok(self.raw_get("frequency")?)
229    }
230
231    pub fn modulation(&self) -> Result<i64> {
232        Ok(self.raw_get("modulation")?)
233    }
234
235    pub fn late_activation(&self) -> bool {
236        self.raw_get("lateActivation").unwrap_or(false)
237    }
238
239    pub fn id(&self) -> Result<GroupId> {
240        Ok(self.raw_get("groupId")?)
241    }
242
243    pub fn set_id(&self, id: GroupId) -> Result<()> {
244        Ok(self.raw_set("groupId", id)?)
245    }
246
247    pub fn tasks(&self) -> Result<Sequence<'lua, Task>> {
248        Ok(self.raw_get("tasks")?)
249    }
250
251    pub fn route(&self) -> Result<Route> {
252        Ok(self.raw_get("route")?)
253    }
254
255    pub fn set_route(&self, r: Route) -> Result<()> {
256        Ok(self.raw_set("route", r)?)
257    }
258
259    pub fn hidden(&self) -> bool {
260        self.raw_get("hidden").unwrap_or(false)
261    }
262
263    pub fn units(&self) -> Result<Sequence<'lua, Unit<'lua>>> {
264        Ok(self.raw_get("units")?)
265    }
266
267    pub fn uncontrolled(&self) -> bool {
268        self.raw_get("uncontrolled").unwrap_or(true)
269    }
270}
271
272wrapped_table!(Country, None);
273
274impl<'lua> Country<'lua> {
275    pub fn id(&self) -> Result<country::Country> {
276        Ok(self.raw_get("id")?)
277    }
278
279    pub fn name(&self) -> Result<String> {
280        Ok(self.raw_get("name")?)
281    }
282
283    pub fn planes(&self) -> Result<Sequence<'lua, Group<'lua>>> {
284        let g: Option<mlua::Table> = self.raw_get("plane")?;
285        g.map(|g| Ok(g.raw_get("group")?))
286            .unwrap_or_else(|| Sequence::empty(self.lua))
287    }
288
289    pub fn helicopters(&self) -> Result<Sequence<'lua, Group<'lua>>> {
290        let g: Option<mlua::Table> = self.raw_get("helicopter")?;
291        g.map(|g| Ok(g.raw_get("group")?))
292            .unwrap_or_else(|| Sequence::empty(self.lua))
293    }
294
295    pub fn ships(&self) -> Result<Sequence<'lua, Group<'lua>>> {
296        let g: Option<mlua::Table> = self.raw_get("ship")?;
297        g.map(|g| Ok(g.raw_get("group")?))
298            .unwrap_or_else(|| Sequence::empty(self.lua))
299    }
300
301    pub fn vehicles(&self) -> Result<Sequence<'lua, Group<'lua>>> {
302        let g: Option<mlua::Table> = self.raw_get("vehicle")?;
303        g.map(|g| Ok(g.raw_get("group")?))
304            .unwrap_or_else(|| Sequence::empty(self.lua))
305    }
306
307    pub fn statics(&self) -> Result<Sequence<'lua, Group<'lua>>> {
308        let g: Option<mlua::Table> = self.raw_get("static")?;
309        g.map(|g| Ok(g.raw_get("group")?))
310            .unwrap_or_else(|| Sequence::empty(self.lua))
311    }
312}
313
314wrapped_table!(Coalition, None);
315
316impl<'lua> Coalition<'lua> {
317    pub fn bullseye(&self) -> Result<LuaVec2> {
318        Ok(self.t.raw_get("bullseye")?)
319    }
320
321    pub fn nav_points(&self) -> Result<Sequence<'lua, NavPoint<'lua>>> {
322        Ok(self.t.raw_get("nav_points")?)
323    }
324
325    pub fn name(&self) -> Result<String> {
326        Ok(self.t.raw_get("name")?)
327    }
328
329    pub fn countries(&self) -> Result<Sequence<'lua, Country<'lua>>> {
330        Ok(self.t.raw_get("country")?)
331    }
332
333    pub fn country(&self, country: country::Country) -> Result<Option<Country<'lua>>> {
334        for c in self.countries()? {
335            let c = c?;
336            if c.id()? == country {
337                return Ok(Some(c));
338            }
339        }
340        return Ok(None);
341    }
342
343    fn index(&self, side: Side, base: Path) -> Result<CoalitionIndex> {
344        let base = base.append(["country"]);
345        let mut idx = CoalitionIndex::default();
346        for (i, country) in self.countries()?.into_iter().enumerate() {
347            let country = country?;
348            let cid = country.id()?;
349            let base = base.append([i + 1]);
350            macro_rules! index_group {
351                ($name:literal, $cat:expr, $tbl:ident) => {
352                    for (i, group) in country.$tbl()?.into_iter().enumerate() {
353                        let group = group?;
354                        let name = group.name()?;
355                        let gid = group.id()?;
356                        idx.max_gid = GroupId(max(idx.max_gid.0, gid.0));
357                        let base = base.append([$name, "group"]).append([i + 1]);
358                        match idx.groups.entry(gid) {
359                            Entry::Occupied(_) => bail!("duplicate group id {:?}", gid),
360                            Entry::Vacant(e) => {
361                                e.insert(IndexedGroup {
362                                    side,
363                                    country: cid,
364                                    category: $cat,
365                                    path: base.clone(),
366                                });
367                            }
368                        }
369                        match idx.groups_by_name.entry(name.clone()) {
370                            Entry::Occupied(_) => bail!("duplicate group name {name}"),
371                            Entry::Vacant(e) => e.insert(gid),
372                        };
373                        match idx.$tbl.entry(name.clone()) {
374                            Entry::Occupied(_) => bail!("duplicate group name {name}"),
375                            Entry::Vacant(e) => e.insert(gid),
376                        };
377                        for (i, unit) in group.units()?.into_iter().enumerate() {
378                            let unit = unit?;
379                            let base = base.append(["units"]).append([i + 1]);
380                            let name = unit.name()?;
381                            let uid = unit.id()?;
382                            idx.max_uid = UnitId(max(idx.max_uid.0, uid.0));
383                            match idx.units.entry(uid) {
384                                Entry::Occupied(_) => bail!("duplicate unit id {:?}", uid),
385                                Entry::Vacant(e) => e.insert(IndexedUnit {
386                                    side,
387                                    country: cid,
388                                    path: base.clone(),
389                                }),
390                            };
391                            match idx.units_by_name.entry(name.clone()) {
392                                Entry::Occupied(_) => bail!("duplicate unit name {name}"),
393                                Entry::Vacant(e) => e.insert(uid),
394                            };
395                            match idx.groups_by_unit.entry(uid) {
396                                Entry::Occupied(_) => bail!("guplicate unit id {:?}", uid),
397                                Entry::Vacant(e) => e.insert(gid),
398                            };
399                        }
400                    }
401                };
402            }
403            index_group!("plane", GroupKind::Plane, planes);
404            index_group!("helicopter", GroupKind::Helicopter, helicopters);
405            index_group!("ship", GroupKind::Ship, ships);
406            index_group!("vehicle", GroupKind::Vehicle, vehicles);
407            index_group!("static", GroupKind::Static, statics);
408        }
409        Ok(idx)
410    }
411}
412
413#[derive(Debug, Clone, Copy)]
414pub struct Role {
415    pub neutrals: u8,
416    pub red: u8,
417    pub blue: u8,
418}
419
420impl<'lua> FromLua<'lua> for Role {
421    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
422        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
423        Ok(Self {
424            neutrals: tbl.raw_get("neutrals")?,
425            red: tbl.raw_get("red")?,
426            blue: tbl.raw_get("blue")?,
427        })
428    }
429}
430
431wrapped_table!(Roles, None);
432
433impl<'lua> Roles<'lua> {
434    pub fn artillery_commander(&self) -> Result<Role> {
435        Ok(self.raw_get("artillery_commander")?)
436    }
437
438    pub fn instructor(&self) -> Result<Role> {
439        Ok(self.raw_get("instructor")?)
440    }
441
442    pub fn observer(&self) -> Result<Role> {
443        Ok(self.raw_get("observer")?)
444    }
445
446    pub fn forward_observer(&self) -> Result<Role> {
447        Ok(self.raw_get("forward_observer")?)
448    }
449}
450
451wrapped_table!(GroundControl, None);
452
453impl<'lua> GroundControl<'lua> {
454    pub fn roles(&self) -> Result<Roles<'lua>> {
455        Ok(self.raw_get("roles")?)
456    }
457}
458
459#[derive(Debug, Clone, Serialize)]
460struct IndexedGroup {
461    side: Side,
462    country: country::Country,
463    category: GroupKind,
464    path: Path,
465}
466
467#[derive(Debug, Clone, Serialize)]
468struct IndexedUnit {
469    side: Side,
470    country: country::Country,
471    path: Path,
472}
473
474#[derive(Debug, Clone, Serialize, Default)]
475pub struct CoalitionIndex {
476    max_uid: UnitId,
477    max_gid: GroupId,
478    units: FxHashMap<UnitId, IndexedUnit>,
479    units_by_name: FxHashMap<String, UnitId>,
480    groups: FxHashMap<GroupId, IndexedGroup>,
481    groups_by_name: FxHashMap<String, GroupId>,
482    groups_by_unit: FxHashMap<UnitId, GroupId>,
483    planes: FxHashMap<String, GroupId>,
484    helicopters: FxHashMap<String, GroupId>,
485    ships: FxHashMap<String, GroupId>,
486    vehicles: FxHashMap<String, GroupId>,
487    statics: FxHashMap<String, GroupId>,
488}
489
490#[derive(Debug, Clone, Serialize, Default)]
491pub struct MizIndex {
492    by_side: FxHashMap<Side, CoalitionIndex>,
493    triggers: FxHashMap<String, Path>,
494}
495
496impl MizIndex {
497    pub fn max_uid(&self) -> UnitId {
498        self.by_side
499            .iter()
500            .fold(UnitId::default(), |muid, (_, cidx)| {
501                UnitId(max(muid.0, cidx.max_uid.0))
502            })
503    }
504
505    pub fn max_gid(&self) -> GroupId {
506        self.by_side
507            .iter()
508            .fold(GroupId::default(), |mgid, (_, cidx)| {
509                GroupId(max(mgid.0, cidx.max_gid.0))
510            })
511    }
512}
513
514#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
515pub enum GroupKind {
516    Any,
517    Plane,
518    Helicopter,
519    Ship,
520    Vehicle,
521    Static,
522}
523
524#[derive(Debug, Clone, Serialize)]
525pub struct GroupInfo<'lua> {
526    pub side: Side,
527    pub country: country::Country,
528    pub category: GroupKind,
529    pub group: Group<'lua>,
530}
531
532#[derive(Debug, Clone, Serialize)]
533pub struct UnitInfo<'lua> {
534    pub side: Side,
535    pub country: country::Country,
536    pub unit: Unit<'lua>,
537}
538
539wrapped_table!(Miz, None);
540
541impl<'lua> Miz<'lua> {
542    pub fn singleton<L: LuaEnv<'lua> + Copy>(lua: L) -> Result<Self> {
543        if is_hooks_env(lua.inner()) {
544            let current: mlua::Table = lua.inner().globals().get("_current_mission")?;
545            Ok(current.get("mission")?)
546        } else {
547            let env: mlua::Table = lua.inner().globals().get("env")?;
548            Ok(env.get("mission")?)
549        }
550    }
551
552    pub fn ground_control(&self) -> Result<GroundControl> {
553        Ok(self.raw_get("groundControl")?)
554    }
555
556    pub fn coalition(&self, side: Side) -> Result<Coalition<'lua>> {
557        let coa: mlua::Table = self.raw_get("coalition")?;
558        Ok(coa.raw_get(side.to_str())?)
559    }
560
561    pub fn triggers(&self) -> Result<Sequence<'lua, TriggerZone<'lua>>> {
562        let triggers: mlua::Table = self.t.raw_get("triggers")?;
563        Ok(triggers.raw_get("zones")?)
564    }
565
566    pub fn weather(&self) -> Result<Weather<'lua>> {
567        Ok(self.t.raw_get("weather")?)
568    }
569
570    pub fn get_group(&self, idx: &MizIndex, id: &GroupId) -> Result<Option<GroupInfo<'lua>>> {
571        idx.by_side
572            .iter()
573            .find_map(|(_, idx)| idx.groups.get(id))
574            .map(|ifo| {
575                self.raw_get_path(&ifo.path).map(|group| GroupInfo {
576                    side: ifo.side,
577                    country: ifo.country,
578                    category: ifo.category,
579                    group,
580                })
581            })
582            .transpose()
583    }
584
585    pub fn get_group_by_name(
586        &self,
587        idx: &MizIndex,
588        kind: GroupKind,
589        side: Side,
590        name: &str,
591    ) -> Result<Option<GroupInfo<'lua>>> {
592        idx.by_side
593            .get(&side)
594            .and_then(|cidx| match kind {
595                GroupKind::Any => cidx.groups_by_name.get(name),
596                GroupKind::Plane => cidx.planes.get(name),
597                GroupKind::Helicopter => cidx.helicopters.get(name),
598                GroupKind::Vehicle => cidx.vehicles.get(name),
599                GroupKind::Ship => cidx.ships.get(name),
600                GroupKind::Static => cidx.statics.get(name),
601            })
602            .and_then(|gid| self.get_group(idx, gid).transpose())
603            .transpose()
604    }
605
606    pub fn get_unit(&self, idx: &MizIndex, id: &UnitId) -> Result<Option<UnitInfo<'lua>>> {
607        idx.by_side
608            .iter()
609            .find_map(|(_, idx)| idx.units.get(id))
610            .map(|ifo| {
611                self.raw_get_path(&ifo.path).map(|unit| UnitInfo {
612                    side: ifo.side,
613                    country: ifo.country,
614                    unit,
615                })
616            })
617            .transpose()
618    }
619
620    pub fn get_unit_by_name(&self, idx: &MizIndex, name: &str) -> Result<Option<UnitInfo<'lua>>> {
621        idx.by_side
622            .iter()
623            .find_map(|(_, idx)| idx.units_by_name.get(name).and_then(|id| idx.units.get(id)))
624            .map(|ifo| {
625                self.raw_get_path(&ifo.path).map(|unit| UnitInfo {
626                    side: ifo.side,
627                    country: ifo.country,
628                    unit,
629                })
630            })
631            .transpose()
632    }
633
634    pub fn get_group_by_unit(
635        &self,
636        idx: &MizIndex,
637        id: &UnitId,
638    ) -> Result<Option<GroupInfo<'lua>>> {
639        idx.by_side
640            .iter()
641            .find_map(|(_, idx)| idx.groups_by_unit.get(id))
642            .and_then(|gid| self.get_group(idx, gid).transpose())
643            .transpose()
644    }
645
646    pub fn get_group_by_unit_name(
647        &self,
648        idx: &MizIndex,
649        name: &str,
650    ) -> Result<Option<GroupInfo<'lua>>> {
651        idx.by_side
652            .iter()
653            .find_map(|(_, idx)| {
654                idx.units_by_name
655                    .get(name)
656                    .and_then(|uid| idx.groups_by_unit.get(uid))
657            })
658            .and_then(|gid| self.get_group(idx, gid).transpose())
659            .transpose()
660    }
661
662    pub fn get_trigger_zone(
663        &self,
664        idx: &MizIndex,
665        name: &str,
666    ) -> Result<Option<TriggerZone<'lua>>> {
667        idx.triggers
668            .get(name)
669            .map(|path| self.raw_get_path(path))
670            .transpose()
671    }
672
673    pub fn sortie(&self) -> Result<String> {
674        Ok(self.raw_get("sortie")?)
675    }
676
677    pub fn index(&self) -> Result<MizIndex> {
678        let base = Path::default();
679        let mut idx = MizIndex::default();
680        {
681            let base = base.append(["triggers", "zones"]);
682            for (i, tz) in self.triggers()?.into_iter().enumerate() {
683                let tz = tz?;
684                let base = base.append([i + 1]);
685                let name = tz.name()?;
686                match idx.triggers.entry(name.clone()) {
687                    Entry::Vacant(e) => e.insert(base),
688                    Entry::Occupied(_) => bail!("duplicate trigger zone {name}"),
689                };
690            }
691        }
692        for side in Side::ALL {
693            let base = base.append(["coalition", side.to_str()]);
694            idx.by_side
695                .entry(side)
696                .or_insert(self.coalition(side)?.index(side, base)?);
697        }
698        Ok(idx)
699    }
700}