dcso3/
controller.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, attribute::Attributes, cvt_err, object::Object, LuaVec3, String};
15use crate::{
16    airbase::{AirbaseId, RunwayId},
17    attribute::Attribute,
18    bitflags_enum,
19    env::miz::{GroupId, TriggerZoneId, UnitId},
20    err, lua_err, simple_enum,
21    static_object::StaticObjectId,
22    string_enum,
23    trigger::Modulation,
24    wrapped_table, LuaVec2, Sequence, Time,
25};
26use anyhow::Result;
27use compact_str::format_compact;
28use enumflags2::{bitflags, BitFlags};
29use mlua::{prelude::*, Value, Variadic};
30use na::Vector2;
31use serde_derive::{Deserialize, Serialize};
32use std::{mem, ops::Deref};
33
34string_enum!(PointType, u8, [
35    TakeOffGround => "TakeOffGround",
36    TakeOffGroundHot => "TakeOffGroundHot",
37    TurningPoint => "Turning Point",
38    TakeOffParking => "TakeOffParking",
39    TakeOff => "TakeOff",
40    Land => "Land",
41    Nil => ""
42]);
43
44string_enum!(WeaponExpend, u8, [
45    Quarter => "Quarter",
46    Two => "Two",
47    One => "One",
48    Four => "Four",
49    Half => "Half",
50    All => "All"
51]);
52
53string_enum!(OrbitPattern, u8, [
54    RaceTrack => "Race-Track",
55    Circle => "Circle"
56]);
57
58string_enum!(TurnMethod, u8, [
59    FlyOverPoint => "Fly Over Point",
60    OffRoad => "Off Road"
61]);
62
63string_enum!(Designation, u8, [
64    No => "No",
65    WP => "WP",
66    IrPointer => "IR-Pointer",
67    Laser => "Laser",
68    Auto => "Auto"
69]);
70
71string_enum!(AltType, u8, [
72    BARO => "BARO",
73    RADIO => "RADIO"
74]);
75
76simple_enum!(FACCallsign, u8, [
77    Axeman	=> 1,
78    Darknight => 2,
79    Warrior => 3,
80    Pointer	=> 4,
81    Eyeball	=> 5,
82    Moonbeam => 6,
83    Whiplash => 7,
84    Finger => 8,
85    Pinpoint => 9,
86    Ferret => 10,
87    Shaba => 11,
88    Playboy	=> 12,
89    Hammer => 13,
90    Jaguar => 14,
91    Deathstar => 15,
92    Anvil => 16,
93    Firefly => 17,
94    Mantis => 18,
95    Badger => 19
96]);
97
98#[derive(Debug, Clone)]
99pub struct AttackParams {
100    pub weapon_type: Option<u64>, // weapon flag(s)
101    pub expend: Option<WeaponExpend>,
102    pub direction: Option<f64>, // in radians
103    pub altitude: Option<f64>,
104    pub attack_qty: Option<i64>,
105    pub group_attack: Option<bool>,
106}
107
108impl<'lua> FromLua<'lua> for AttackParams {
109    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
110        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
111        Ok(Self {
112            weapon_type: tbl.raw_get("weaponType")?,
113            expend: tbl.raw_get("expend")?,
114            direction: if tbl.raw_get("directionEnabled")? {
115                tbl.raw_get("direction")?
116            } else {
117                None
118            },
119            altitude: if tbl.raw_get("altitudeEnabled")? {
120                tbl.raw_get("altitude")?
121            } else {
122                None
123            },
124            attack_qty: if tbl.raw_get("attackQtyLimit")? {
125                tbl.raw_get("attackQty")?
126            } else {
127                None
128            },
129            group_attack: tbl.raw_get("groupAttack")?,
130        })
131    }
132}
133
134impl AttackParams {
135    fn push_tbl(&self, tbl: &LuaTable) -> LuaResult<()> {
136        if let Some(wt) = self.weapon_type {
137            tbl.raw_set("weaponType", wt)?
138        }
139        if let Some(exp) = &self.expend {
140            tbl.raw_set("expend", exp.clone())?
141        }
142        if let Some(dir) = self.direction {
143            tbl.raw_set("directionEnabled", true)?;
144            tbl.raw_set("direction", dir)?
145        }
146        if let Some(alt) = self.altitude {
147            tbl.raw_set("altitudeEnabled", true)?;
148            tbl.raw_set("altitude", alt)?;
149        }
150        if let Some(qty) = self.attack_qty {
151            tbl.raw_set("attackQtyLimit", true)?;
152            tbl.raw_set("attackQty", qty)?;
153        }
154        if let Some(grp) = self.group_attack {
155            tbl.raw_set("groupAttack", grp)?;
156        }
157        Ok(())
158    }
159}
160
161#[derive(Debug, Clone)]
162pub struct FollowParams {
163    pub group: GroupId,
164    pub pos: LuaVec3,
165    pub last_waypoint_index: Option<i64>,
166}
167
168impl<'lua> FromLua<'lua> for FollowParams {
169    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
170        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
171        Ok(Self {
172            group: tbl.raw_get("groupId")?,
173            pos: tbl.raw_get("pos")?,
174            last_waypoint_index: if tbl.raw_get("lastWptIndexFlag")? {
175                tbl.raw_get("lastWptIndex")?
176            } else {
177                None
178            },
179        })
180    }
181}
182
183impl FollowParams {
184    fn push_tbl(&self, tbl: &LuaTable) -> LuaResult<()> {
185        tbl.raw_set("groupId", self.group)?;
186        tbl.raw_set("pos", self.pos)?;
187        if let Some(idx) = self.last_waypoint_index {
188            tbl.raw_set("lastWptIndexFlag", true)?;
189            tbl.raw_set("lastWptIndex", idx)?
190        }
191        Ok(())
192    }
193}
194
195#[derive(Debug, Clone)]
196pub struct FACParams {
197    pub weapon_type: Option<u64>, // weapon flag(s),
198    pub designation: Option<Designation>,
199    pub datalink: Option<bool>,
200    pub frequency: Option<f64>,
201    pub modulation: Option<Modulation>,
202    pub callname: Option<FACCallsign>,
203    pub number: Option<u8>,
204}
205
206impl<'lua> FromLua<'lua> for FACParams {
207    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
208        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
209        Ok(Self {
210            weapon_type: tbl.raw_get("weaponType")?,
211            designation: tbl.raw_get("designation")?,
212            datalink: tbl.raw_get("datalink")?,
213            frequency: tbl.raw_get("frequency")?,
214            modulation: tbl.raw_get("modulation")?,
215            callname: tbl.raw_get("callname")?,
216            number: tbl.raw_get("number")?,
217        })
218    }
219}
220
221impl FACParams {
222    fn push_tbl(&self, tbl: &LuaTable) -> LuaResult<()> {
223        if let Some(wt) = self.weapon_type {
224            tbl.raw_set("weaponType", wt)?;
225        }
226        if let Some(d) = &self.designation {
227            tbl.raw_set("designation", d.clone())?;
228        }
229        if let Some(dl) = self.datalink {
230            tbl.raw_set("datalink", dl)?;
231        }
232        if let Some(frq) = self.frequency {
233            tbl.raw_set("frequency", frq)?;
234        }
235        if let Some(md) = self.modulation {
236            tbl.raw_set("modulation", md)?;
237        }
238        if let Some(cn) = self.callname {
239            tbl.raw_set("callname", cn)?;
240        }
241        if let Some(n) = self.number {
242            tbl.raw_set("number", n)?;
243        }
244        Ok(())
245    }
246}
247
248#[derive(Debug, Clone)]
249pub enum ActionTyp {
250    Air(TurnMethod),
251    Ground(VehicleFormation),
252}
253
254impl<'lua> IntoLua<'lua> for ActionTyp {
255    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
256        match self {
257            ActionTyp::Air(a) => IntoLua::into_lua(a, lua),
258            ActionTyp::Ground(a) => IntoLua::into_lua(a, lua),
259        }
260    }
261}
262
263impl<'lua> FromLua<'lua> for ActionTyp {
264    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
265        match TurnMethod::from_lua(value.clone(), lua) {
266            Ok(v) => Ok(Self::Air(v)),
267            Err(te) => match VehicleFormation::from_lua(value, lua) {
268                Ok(v) => Ok(Self::Ground(v)),
269                Err(ve) => Err(err(&format_compact!(
270                    "unknown action turn method: {te:?}, vehicle formation: {ve:?}"
271                ))),
272            },
273        }
274    }
275}
276
277#[derive(Debug, Clone)]
278pub struct MissionPoint<'lua> {
279    pub typ: PointType,
280    pub airdrome_id: Option<AirbaseId>,
281    pub time_re_fu_ar: Option<i64>,
282    pub helipad: Option<AirbaseId>,
283    pub link_unit: Option<UnitId>,
284    pub action: Option<ActionTyp>,
285    pub pos: LuaVec2,
286    pub alt: f64,
287    pub alt_typ: Option<AltType>,
288    pub speed: f64,
289    pub speed_locked: Option<bool>,
290    pub eta: Option<Time>,
291    pub eta_locked: Option<bool>,
292    pub name: Option<String>,
293    pub task: Box<Task<'lua>>,
294}
295
296impl<'lua> FromLua<'lua> for MissionPoint<'lua> {
297    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
298        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
299        Ok(Self {
300            typ: tbl.raw_get("type")?,
301            airdrome_id: tbl.raw_get("airdromId")?,
302            time_re_fu_ar: tbl.raw_get("timeReFuAr")?,
303            helipad: tbl.raw_get("helipadId")?,
304            link_unit: tbl.raw_get("linkUnit")?,
305            action: tbl.raw_get("action")?,
306            pos: LuaVec2(Vector2::new(tbl.raw_get("x")?, tbl.raw_get("y")?)),
307            alt: tbl.raw_get("alt")?,
308            alt_typ: tbl.raw_get("alt_type")?,
309            speed: tbl.raw_get("speed")?,
310            speed_locked: tbl.raw_get("speed_locked")?,
311            eta: tbl.raw_get("ETA")?,
312            eta_locked: tbl.raw_get("ETA_locked")?,
313            name: tbl.raw_get("name")?,
314            task: Box::new(tbl.raw_get("task")?),
315        })
316    }
317}
318
319impl<'lua> IntoLua<'lua> for MissionPoint<'lua> {
320    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
321        let iter = [
322            ("type", self.typ.into_lua(lua)?),
323            ("airdromId", self.airdrome_id.into_lua(lua)?),
324            ("timeReFuAr", self.time_re_fu_ar.into_lua(lua)?),
325            ("helipadId", self.helipad.into_lua(lua)?),
326            ("linkUnit", self.link_unit.into_lua(lua)?),
327            ("action", self.action.into_lua(lua)?),
328            ("x", self.pos.x.into_lua(lua)?),
329            ("y", self.pos.y.into_lua(lua)?),
330            ("alt", self.alt.into_lua(lua)?),
331            ("alt_type", self.alt_typ.into_lua(lua)?),
332            ("speed", self.speed.into_lua(lua)?),
333            ("speed_locked", self.speed_locked.into_lua(lua)?),
334            ("ETA", self.eta.into_lua(lua)?),
335            ("ETA_locked", self.eta_locked.into_lua(lua)?),
336            ("name", self.name.into_lua(lua)?),
337            ("task", self.task.into_lua(lua)?),
338        ]
339        .into_iter()
340        .filter(|(_, v)| !v.is_nil());
341        Ok(Value::Table(lua.create_table_from(iter)?))
342    }
343}
344
345#[derive(Debug, Clone)]
346pub struct TaskStartCond<'lua> {
347    pub time: Option<Time>,
348    pub user_flag: Option<Value<'lua>>,
349    pub user_flag_value: Option<Value<'lua>>,
350    pub probability: Option<u8>,
351    pub condition: Option<String>, // lua code
352}
353
354impl<'lua> FromLua<'lua> for TaskStartCond<'lua> {
355    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
356        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
357        Ok(Self {
358            time: tbl.raw_get("time")?,
359            user_flag: tbl.raw_get("userFlag")?,
360            user_flag_value: tbl.raw_get("userFlagValue")?,
361            probability: tbl.raw_get("probability")?,
362            condition: tbl.raw_get("condition")?,
363        })
364    }
365}
366
367impl<'lua> IntoLua<'lua> for TaskStartCond<'lua> {
368    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
369        let iter = [
370            ("time", self.time.into_lua(lua)?),
371            ("userFlag", self.user_flag.into_lua(lua)?),
372            ("userFlagValue", self.user_flag_value.into_lua(lua)?),
373            ("probability", self.probability.into_lua(lua)?),
374            ("condition", self.condition.into_lua(lua)?),
375        ]
376        .into_iter()
377        .filter(|(_, v)| !v.is_nil());
378        Ok(Value::Table(lua.create_table_from(iter)?))
379    }
380}
381
382#[derive(Debug, Clone)]
383pub struct TaskStopCond<'lua> {
384    pub time: Option<Time>,
385    pub user_flag: Option<Value<'lua>>,
386    pub user_flag_value: Option<Value<'lua>>,
387    pub last_waypoint: Option<i64>,
388    pub duration: Option<Time>,
389    pub condition: Option<String>, // lua code
390}
391
392impl<'lua> FromLua<'lua> for TaskStopCond<'lua> {
393    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
394        let tbl: LuaTable = FromLua::from_lua(value, lua)?;
395        Ok(Self {
396            time: tbl.raw_get("time")?,
397            user_flag: tbl.raw_get("userFlag")?,
398            user_flag_value: tbl.raw_get("userFlagValue")?,
399            last_waypoint: tbl.raw_get("lastWaypoint")?,
400            duration: tbl.raw_get("duration")?,
401            condition: tbl.raw_get("condition")?,
402        })
403    }
404}
405
406impl<'lua> IntoLua<'lua> for TaskStopCond<'lua> {
407    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
408        let iter = [
409            ("time", self.time.into_lua(lua)?),
410            ("userFlag", self.user_flag.into_lua(lua)?),
411            ("userFlagValue", self.user_flag_value.into_lua(lua)?),
412            ("lastWaypoint", self.last_waypoint.into_lua(lua)?),
413            ("duration", self.duration.into_lua(lua)?),
414            ("condition", self.condition.into_lua(lua)?),
415        ]
416        .into_iter()
417        .filter(|(_, v)| !v.is_nil());
418        Ok(Value::Table(lua.create_table_from(iter)?))
419    }
420}
421
422#[derive(Debug, Clone)]
423pub enum Task<'lua> {
424    AttackGroup {
425        group: GroupId,
426        params: AttackParams,
427    },
428    AttackUnit {
429        unit: UnitId,
430        params: AttackParams,
431    },
432    Bombing {
433        point: LuaVec2,
434        params: AttackParams,
435    },
436    Strafing {
437        point: LuaVec2,
438        length: f64,
439        params: AttackParams,
440    },
441    CarpetBombing {
442        point: LuaVec2,
443        carpet_length: f64,
444        params: AttackParams,
445    },
446    AttackMapObject {
447        point: LuaVec2,
448        params: AttackParams,
449    },
450    BombRunway {
451        runway: RunwayId,
452        params: AttackParams,
453    },
454    Orbit {
455        pattern: OrbitPattern,
456        point: Option<LuaVec2>,
457        point2: Option<LuaVec2>,
458        speed: Option<f64>,
459        altitude: Option<f64>,
460    },
461    Refuelling,
462    Land {
463        point: LuaVec2,
464        duration: Option<Time>,
465    },
466    Follow(FollowParams),
467    FollowBigFormation(FollowParams),
468    Escort {
469        engagement_dist_max: f64,
470        target_types: Vec<Attribute>,
471        params: FollowParams,
472    },
473    Embarking {
474        pos: LuaVec2,
475        groups_for_embarking: Vec<GroupId>,
476        duration: Option<Time>,
477        distribution: Option<Vec<UnitId>>,
478    },
479    FireAtPoint {
480        point: LuaVec2,
481        radius: Option<f64>,
482        expend_qty: Option<i64>,
483        weapon_type: Option<u64>, // weapon flag(s)
484        altitude: Option<f64>,
485        altitude_type: Option<AltType>,
486    },
487    Hold,
488    FACAttackGroup {
489        group: GroupId,
490        params: FACParams,
491    },
492    EmbarkToTransport {
493        pos: LuaVec2,
494        radius: Option<f64>,
495    },
496    DisembarkFromTransport {
497        pos: LuaVec2,
498        radius: Option<f64>,
499    },
500    CargoTransportation {
501        group: Option<StaticObjectId>,
502        zone: Option<TriggerZoneId>,
503    },
504    GoToWaypoint {
505        from_waypoint: i64,
506        to_waypoint: i64,
507    },
508    GroundEscort {
509        group: GroupId,
510        engagement_max_distance: f64,
511        target_types: Vec<Attribute>,
512        last_wpt_index: Option<i64>,
513    },
514    RecoveryTanker {
515        group: GroupId,
516        speed: f64,
517        altitude: f64,
518        last_wpt_idx: Option<i64>,
519    },
520    EngageTargets {
521        target_types: Vec<Attribute>,
522        max_dist: Option<f64>,
523        priority: Option<i64>,
524    },
525    EngageTargetsInZone {
526        point: LuaVec2,
527        zone_radius: f64,
528        target_types: Vec<Attribute>,
529        priority: Option<i64>,
530    },
531    EngageGroup {
532        group: GroupId,
533        params: AttackParams,
534        priority: Option<i64>,
535    },
536    EngageUnit {
537        unit: UnitId,
538        params: AttackParams,
539        priority: Option<i64>,
540    },
541    AWACS,
542    Tanker,
543    EWR,
544    FACEngageGroup {
545        group: GroupId,
546        params: FACParams,
547        priority: Option<i64>,
548    },
549    FAC {
550        params: FACParams,
551        priority: Option<i64>,
552    },
553    Mission {
554        airborne: Option<bool>,
555        route: Vec<MissionPoint<'lua>>,
556    },
557    ComboTask(Vec<Task<'lua>>),
558    ControlledTask {
559        task: Box<Task<'lua>>,
560        condition: TaskStartCond<'lua>,
561        stop_condition: TaskStopCond<'lua>,
562    },
563    WrappedCommand(Command),
564    WrappedOption(AiOption<'lua>),
565}
566
567impl<'lua> FromLua<'lua> for Task<'lua> {
568    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
569        let root: LuaTable = FromLua::from_lua(value, lua)?;
570        let id: String = root.raw_get("id")?;
571        let params = match root.raw_get::<_, Option<LuaTable>>("params")? {
572            Some(tbl) => tbl,
573            None => lua.create_table()?,
574        };
575        match id.as_str() {
576            "AttackGroup" => Ok(Self::AttackGroup {
577                group: params.raw_get("groupId")?,
578                params: FromLua::from_lua(Value::Table(params), lua)?,
579            }),
580            "AttackUnit" => Ok(Self::AttackUnit {
581                unit: params.raw_get("unitId")?,
582                params: FromLua::from_lua(Value::Table(params), lua)?,
583            }),
584            "Bombing" => Ok(Self::Bombing {
585                point: params.raw_get("point")?,
586                params: FromLua::from_lua(Value::Table(params), lua)?,
587            }),
588            "Strafing" => Ok(Self::Strafing {
589                point: params.raw_get("point")?,
590                length: params.raw_get("length")?,
591                params: FromLua::from_lua(Value::Table(params), lua)?,
592            }),
593            "CarpetBombing" => Ok(Self::CarpetBombing {
594                point: params.raw_get("point")?,
595                carpet_length: params.raw_get("carpetLength")?,
596                params: FromLua::from_lua(Value::Table(params), lua)?,
597            }),
598            "AttackMapObject" => Ok(Self::AttackMapObject {
599                point: params.raw_get("point")?,
600                params: FromLua::from_lua(Value::Table(params), lua)?,
601            }),
602            "BombingRunway" => Ok(Self::BombRunway {
603                runway: params.raw_get("runwayId")?,
604                params: FromLua::from_lua(Value::Table(params), lua)?,
605            }),
606            "Orbit" => Ok(Self::Orbit {
607                pattern: params.raw_get("pattern")?,
608                point: params.raw_get("point")?,
609                point2: params.raw_get("point2")?,
610                speed: params.raw_get("speed")?,
611                altitude: params.raw_get("altitude")?,
612            }),
613            "Refueling" => Ok(Self::Refuelling),
614            "Follow" => Ok(Self::Follow(FromLua::from_lua(Value::Table(params), lua)?)),
615            "FollowBigFormation" => Ok(Self::FollowBigFormation(FromLua::from_lua(
616                Value::Table(params),
617                lua,
618            )?)),
619            "Escort" => Ok(Self::Escort {
620                engagement_dist_max: params.raw_get("engagementDistMax")?,
621                target_types: params.raw_get("targetTypes")?,
622                params: FromLua::from_lua(Value::Table(params), lua)?,
623            }),
624            "Embarking" => Ok(Self::Embarking {
625                pos: LuaVec2(Vector2::new(params.raw_get("x")?, params.raw_get("y")?)),
626                groups_for_embarking: params.raw_get("groupsForEmbarking")?,
627                duration: params.raw_get("duration")?,
628                distribution: params.raw_get("distribution")?,
629            }),
630            "FireAtPoint" => Ok(Self::FireAtPoint {
631                point: params.raw_get("point")?,
632                radius: params.raw_get("radius")?,
633                expend_qty: if params.raw_get("expendQtyEnabled")? {
634                    params.raw_get("expendQty")?
635                } else {
636                    None
637                },
638                weapon_type: params.raw_get("weaponType")?,
639                altitude: params.raw_get("altitude")?,
640                altitude_type: params.raw_get("alt_type")?,
641            }),
642            "Hold" => Ok(Self::Hold),
643            "FAC_AttackGroup" => Ok(Self::FACAttackGroup {
644                group: params.raw_get("groupId")?,
645                params: FromLua::from_lua(Value::Table(params), lua)?,
646            }),
647            "EmbarkToTransport" => Ok(Self::EmbarkToTransport {
648                pos: LuaVec2(Vector2::new(params.raw_get("x")?, params.raw_get("y")?)),
649                radius: params.raw_get("zoneRadius")?,
650            }),
651            "DisembarkFromTransport" => Ok(Self::DisembarkFromTransport {
652                pos: LuaVec2(Vector2::new(params.raw_get("x")?, params.raw_get("y")?)),
653                radius: params.raw_get("zoneRadius")?,
654            }),
655            "CargoTransportation" => Ok(Self::CargoTransportation {
656                group: params.raw_get("groupId")?,
657                zone: params.raw_get("zoneId")?,
658            }),
659            // "goToGulag" => Ok(Self::GoToGulag),
660            "goToWaypoint" => Ok(Self::GoToWaypoint {
661                from_waypoint: params.raw_get("fromWaypointIndex")?,
662                to_waypoint: params.raw_get("goToWaypointIndex")?,
663            }),
664            "GroundEscort" => Ok(Self::GroundEscort {
665                group: params.raw_get("groupId")?,
666                engagement_max_distance: params.raw_get("engagementDistMax")?,
667                target_types: params.raw_get("targetTypes")?,
668                last_wpt_index: if params.raw_get("lastWptIndexFlag")? {
669                    params.raw_get("lastWptIndex")?
670                } else {
671                    None
672                },
673            }),
674            "RecoveryTanker" => Ok(Self::RecoveryTanker {
675                group: params.raw_get("groupId")?,
676                speed: params.raw_get("speed")?,
677                altitude: params.raw_get("altitude")?,
678                last_wpt_idx: if params.raw_get("lastWptIndexFlag")? {
679                    params.raw_get("lastWptIndex")?
680                } else {
681                    None
682                },
683            }),
684            "EngageTargets" => Ok(Self::EngageTargets {
685                target_types: params.raw_get("targetTypes")?,
686                max_dist: params.raw_get("maxDist")?,
687                priority: params.raw_get("priority")?,
688            }),
689            "EngageTargetsInZone" => Ok(Self::EngageTargetsInZone {
690                point: params.raw_get("point")?,
691                zone_radius: params.raw_get("zoneRadius")?,
692                target_types: params.raw_get("targetTypes")?,
693                priority: params.raw_get("priority")?,
694            }),
695            "EngageGroup" => Ok(Self::EngageGroup {
696                group: params.raw_get("groupId")?,
697                priority: params.raw_get("priority")?,
698                params: FromLua::from_lua(Value::Table(params), lua)?,
699            }),
700            "EngageUnit" => Ok(Self::EngageUnit {
701                unit: params.raw_get("unitId")?,
702                priority: params.raw_get("priority")?,
703                params: FromLua::from_lua(Value::Table(params), lua)?,
704            }),
705            "AWACS" => Ok(Self::AWACS),
706            "Tanker" => Ok(Self::Tanker),
707            "EWR" => Ok(Self::EWR),
708            "FAC_EngageGroup" => Ok(Self::FACEngageGroup {
709                group: params.raw_get("groupId")?,
710                priority: params.raw_get("priority")?,
711                params: FromLua::from_lua(Value::Table(params), lua)?,
712            }),
713            "FAC" => Ok(Self::FAC {
714                priority: params.raw_get("priority")?,
715                params: FromLua::from_lua(Value::Table(params), lua)?,
716            }),
717            "Mission" => Ok(Self::Mission {
718                airborne: params.raw_get("airborne")?,
719                route: FromLua::from_lua(Value::Table(params), lua)?,
720            }),
721            "ComboTask" => Ok(Self::ComboTask(FromLua::from_lua(
722                Value::Table(params.raw_get("tasks")?),
723                lua,
724            )?)),
725            "ControlledTask" => Ok(Self::ControlledTask {
726                task: Box::new(params.raw_get("task")?),
727                condition: params.raw_get("condition")?,
728                stop_condition: params.raw_get("stopCondition")?,
729            }),
730            "WrappedAction" => {
731                let action: LuaTable = params.raw_get("action")?;
732                match action.raw_get::<_, String>("id")?.as_str() {
733                    "Option" => {
734                        let params: LuaTable = action.raw_get("params")?;
735                        let tag: u8 = params.raw_get("name")?;
736                        let val: Value = params.raw_get("value")?;
737                        Ok(Self::WrappedOption(AiOption::from_tag_val(lua, tag, val)?))
738                    }
739                    _cmd => Ok(Self::WrappedCommand(params.raw_get("action")?)),
740                }
741            }
742            s => Err(err(&format_compact!("invalid action {s}"))),
743        }
744    }
745}
746
747impl<'lua> IntoLua<'lua> for Task<'lua> {
748    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
749        let root = lua.create_table()?;
750        let params = lua.create_table()?;
751        match self {
752            Self::AttackGroup { group, params: atp } => {
753                root.raw_set("id", "AttackGroup")?;
754                params.raw_set("groupId", group)?;
755                atp.push_tbl(&params)?;
756            }
757            Self::AttackUnit { unit, params: atp } => {
758                root.raw_set("id", "AttackUnit")?;
759                params.raw_set("unitId", unit)?;
760                atp.push_tbl(&params)?;
761            }
762            Self::Bombing { point, params: atp } => {
763                root.raw_set("id", "Bombing")?;
764                params.raw_set("point", point)?;
765                atp.push_tbl(&params)?;
766            }
767            Self::Strafing {
768                point,
769                length,
770                params: atp,
771            } => {
772                root.raw_set("id", "Strafing")?;
773                params.raw_set("point", point)?;
774                params.raw_set("length", length)?;
775                atp.push_tbl(&params)?;
776            }
777            Self::CarpetBombing {
778                point,
779                carpet_length,
780                params: atp,
781            } => {
782                root.raw_set("id", "CarpetBombing")?;
783                params.raw_set("point", point)?;
784                params.raw_set("carpetLength", carpet_length)?;
785                atp.push_tbl(&params)?;
786            }
787            Self::AttackMapObject { point, params: atp } => {
788                root.raw_set("id", "AttackMapObject")?;
789                params.raw_set("point", point)?;
790                atp.push_tbl(&params)?;
791            }
792            Self::BombRunway {
793                runway,
794                params: atp,
795            } => {
796                root.raw_set("id", "BombingRunway")?;
797                params.raw_set("runwayId", runway)?;
798                atp.push_tbl(&params)?;
799            }
800            Self::Orbit {
801                pattern,
802                point,
803                point2,
804                speed,
805                altitude,
806            } => {
807                root.raw_set("id", "Orbit")?;
808                params.raw_set("pattern", pattern)?;
809                params.raw_set("point", point)?;
810                params.raw_set("point2", point2)?;
811                params.raw_set("speed", speed)?;
812                params.raw_set("altitude", altitude)?;
813            }
814            Self::Refuelling => root.raw_set("id", "Refueling")?,
815            Self::Land { point, duration } => {
816                root.raw_set("id", "Land")?;
817                params.raw_set("point", point)?;
818                if let Some(dur) = duration {
819                    params.raw_set("durationFlag", true)?;
820                    params.raw_set("duration", dur)?;
821                }
822            }
823            Self::Follow(fp) => {
824                root.raw_set("id", "Follow")?;
825                fp.push_tbl(&params)?;
826            }
827            Self::FollowBigFormation(fp) => {
828                root.raw_set("id", "FollowBigFormation")?;
829                fp.push_tbl(&params)?;
830            }
831            Self::Escort {
832                engagement_dist_max,
833                target_types,
834                params: fp,
835            } => {
836                root.raw_set("id", "Escort")?;
837                params.raw_set("engagementDistMax", engagement_dist_max)?;
838                params.raw_set("targetTypes", target_types)?;
839                fp.push_tbl(&params)?;
840            }
841            Self::Embarking {
842                pos,
843                groups_for_embarking,
844                duration,
845                distribution,
846            } => {
847                root.raw_set("id", "Embarking")?;
848                params.raw_set("x", pos.x)?;
849                params.raw_set("y", pos.y)?;
850                params.raw_set("groupsForEmbarking", groups_for_embarking)?;
851                if let Some(dur) = duration {
852                    params.raw_set("duration", dur)?;
853                }
854                if let Some(dist) = distribution {
855                    params.raw_set("distribution", dist)?;
856                }
857            }
858            Self::FireAtPoint {
859                point,
860                radius,
861                expend_qty,
862                weapon_type,
863                altitude,
864                altitude_type,
865            } => {
866                root.raw_set("id", "FireAtPoint")?;
867                params.raw_set("point", point)?;
868                if let Some(radius) = radius {
869                    params.raw_set("radius", radius)?;
870                }
871                if let Some(qty) = expend_qty {
872                    params.raw_set("expendQtyEnabled", true)?;
873                    params.raw_set("expendQty", qty)?;
874                }
875                if let Some(wt) = weapon_type {
876                    params.raw_set("weaponType", wt)?;
877                }
878                if let Some(alt) = altitude {
879                    params.raw_set("altitude", alt)?;
880                }
881                if let Some(at) = altitude_type {
882                    params.raw_set("alt_type", at)?;
883                }
884            }
885            Self::Hold => root.raw_set("id", "Hold")?,
886            Self::FACAttackGroup { group, params: fp } => {
887                root.raw_set("id", "FAC_AttackGroup")?;
888                params.raw_set("groupId", group)?;
889                fp.push_tbl(&params)?;
890            }
891            Self::EmbarkToTransport { pos, radius } => {
892                root.raw_set("id", "EmbarkToTransport")?;
893                params.raw_set("x", pos.x)?;
894                params.raw_set("y", pos.y)?;
895                if let Some(rad) = radius {
896                    params.raw_set("zoneRadius", rad)?
897                }
898            }
899            Self::DisembarkFromTransport { pos, radius } => {
900                root.raw_set("id", "DisembarkFromTransport")?;
901                params.raw_set("x", pos.x)?;
902                params.raw_set("y", pos.y)?;
903                if let Some(rad) = radius {
904                    params.raw_set("zoneRadius", rad)?;
905                }
906            }
907            Self::CargoTransportation { group, zone } => {
908                root.raw_set("id", "CargoTransportation")?;
909                if let Some(gid) = group {
910                    params.raw_set("groupId", gid)?;
911                }
912                if let Some(zone) = zone {
913                    params.raw_set("zoneId", zone)?;
914                }
915            }
916            Self::GoToWaypoint {
917                from_waypoint,
918                to_waypoint,
919            } => {
920                root.raw_set("id", "goToWaypoint")?;
921                params.raw_set("fromWaypointIndex", from_waypoint)?;
922                params.raw_set("goToWaypointIndex", to_waypoint)?;
923            }
924            Self::GroundEscort {
925                group,
926                engagement_max_distance,
927                target_types,
928                last_wpt_index,
929            } => {
930                root.raw_set("id", "GroundEscort")?;
931                params.raw_set("groupId", group)?;
932                params.raw_set("engagementDistMax", engagement_max_distance)?;
933                params.raw_set("targetTypes", target_types)?;
934                if let Some(wpi) = last_wpt_index {
935                    params.raw_set("lastWptIndexFlag", true)?;
936                    params.raw_set("lastWptIndex", wpi)?;
937                }
938            }
939            Self::RecoveryTanker {
940                group,
941                speed,
942                altitude,
943                last_wpt_idx,
944            } => {
945                root.raw_set("id", "RecoveryTanker")?;
946                params.raw_set("groupId", group)?;
947                params.raw_set("speed", speed)?;
948                params.raw_set("altitude", altitude)?;
949                if let Some(idx) = last_wpt_idx {
950                    params.raw_set("lastWptIndexFlag", true)?;
951                    params.raw_set("lastWptIndex", idx)?;
952                }
953            }
954            Self::EngageTargets {
955                target_types,
956                max_dist,
957                priority,
958            } => {
959                root.raw_set("id", "EngageTargets")?;
960                params.raw_set("targetTypes", target_types)?;
961                if let Some(d) = max_dist {
962                    params.raw_set("maxDist", d)?;
963                }
964                if let Some(p) = priority {
965                    params.raw_set("priority", p)?;
966                }
967            }
968            Self::EngageTargetsInZone {
969                point,
970                zone_radius,
971                target_types,
972                priority,
973            } => {
974                root.raw_set("id", "EngageTargetsInZone")?;
975                params.raw_set("point", point)?;
976                params.raw_set("zoneRadius", zone_radius)?;
977                params.raw_set("targetTypes", target_types)?;
978                if let Some(p) = priority {
979                    params.raw_set("priority", p)?;
980                }
981            }
982            Self::EngageGroup {
983                group,
984                params: atp,
985                priority,
986            } => {
987                root.raw_set("id", "EngageGroup")?;
988                params.raw_set("groupId", group)?;
989                atp.push_tbl(&params)?;
990                if let Some(p) = priority {
991                    params.raw_set("priority", p)?;
992                }
993            }
994            Self::EngageUnit {
995                unit,
996                params: atp,
997                priority,
998            } => {
999                root.raw_set("id", "EngageUnit")?;
1000                params.raw_set("unitId", unit)?;
1001                atp.push_tbl(&params)?;
1002                if let Some(p) = priority {
1003                    params.raw_set("priority", p)?;
1004                }
1005            }
1006            Self::AWACS => root.raw_set("id", "AWACS")?,
1007            Self::Tanker => root.raw_set("id", "Tanker")?,
1008            Self::EWR => root.raw_set("id", "EWR")?,
1009            Self::FACEngageGroup {
1010                group,
1011                params: fp,
1012                priority,
1013            } => {
1014                root.raw_set("id", "FAC_EngageGroup")?;
1015                params.raw_set("groupId", group)?;
1016                fp.push_tbl(&params)?;
1017                if let Some(p) = priority {
1018                    params.raw_set("priority", p)?;
1019                }
1020            }
1021            Self::FAC {
1022                params: fp,
1023                priority,
1024            } => {
1025                root.raw_set("id", "FAC")?;
1026                fp.push_tbl(&params)?;
1027                if let Some(p) = priority {
1028                    params.raw_set("priority", p)?;
1029                }
1030            }
1031            Self::Mission { airborne, route } => {
1032                root.raw_set("id", "Mission")?;
1033                params.raw_set("airborne", airborne)?;
1034                let points = lua.create_table()?;
1035                points.raw_set(
1036                    "points",
1037                    route
1038                        .into_iter()
1039                        .map(|m| m.into_lua(lua))
1040                        .collect::<LuaResult<Vec<Value>>>()?,
1041                )?;
1042                params.raw_set("route", points)?;
1043            }
1044            Self::ComboTask(tasks) => {
1045                root.raw_set("id", "ComboTask")?;
1046                let tbl = lua.create_table()?;
1047                for (i, task) in tasks.into_iter().enumerate() {
1048                    tbl.push(task)?;
1049                    tbl.raw_get::<_, LuaTable>(i + 1)?
1050                        .raw_set("number", i + 1)?;
1051                }
1052                params.raw_set("tasks", tbl)?;
1053            }
1054            Self::ControlledTask {
1055                mut task,
1056                condition,
1057                stop_condition,
1058            } => {
1059                let task = mem::replace(task.as_mut(), Task::AWACS);
1060                root.raw_set("id", "ControlledTask")?;
1061                params.raw_set("task", task)?;
1062                params.raw_set("condition", condition)?;
1063                params.raw_set("stopCondition", stop_condition)?;
1064            }
1065            Self::WrappedCommand(cmd) => {
1066                root.raw_set("id", "WrappedAction")?;
1067                let cmd = cmd.into_lua(lua)?;
1068                params.raw_set("action", cmd)?;
1069            }
1070            Self::WrappedOption(val) => {
1071                root.raw_set("id", "WrappedAction")?;
1072                let opt = lua.create_table()?;
1073                let optpar = lua.create_table()?;
1074                opt.raw_set("id", "Option")?;
1075                optpar.raw_set("name", val.tag())?;
1076                optpar.raw_set("value", val)?;
1077                opt.raw_set("params", optpar)?;
1078                params.raw_set("action", opt)?;
1079            }
1080        }
1081        root.raw_set("params", params)?;
1082        Ok(Value::Table(root))
1083    }
1084}
1085
1086simple_enum!(BeaconType, u16, [
1087    Null => 0,
1088    VOR => 1,
1089    DME => 2,
1090    VORDME => 3,
1091    TACAN => 4,
1092    VORTAC => 5,
1093    RSBN => 32,
1094    BroadcastStation => 1024,
1095    Homer => 8,
1096    AirportHomer => 4104,
1097    AirportHomerWithMarker => 4136,
1098    ILSFarHomer => 16408,
1099    ILSNearHomer => 16456,
1100    ILSLocalizer => 16640,
1101    ILSGlideslope => 16896,
1102    NauticalHomer => 32776
1103]);
1104
1105simple_enum!(BeaconSystem, u8, [
1106    PAR10 => 1,
1107    RSBN5 => 2,
1108    TACAN => 3,
1109    TACANTanker => 4,
1110    ILSLocalizer => 5,
1111    ILSGlideslope => 6,
1112    BroadcastStation => 7
1113]);
1114
1115#[derive(Debug, Clone)]
1116pub enum Command {
1117    Script(String),
1118    SetCallsign {
1119        callname: i64,
1120        number: u8,
1121    },
1122    SetFrequency {
1123        frequency: i64,
1124        modulation: Modulation,
1125        power: i64,
1126    },
1127    SetFrequencyForUnit {
1128        frequency: i64,
1129        modulation: Modulation,
1130        power: i64,
1131        unit: UnitId,
1132    },
1133    SwitchWaypoint {
1134        from_waypoint: i64,
1135        to_waypoint: i64,
1136    },
1137    StopRoute(bool),
1138    SwitchAction(i64),
1139    SetInvisible(bool),
1140    SetImmortal(bool),
1141    SetUnlimitedFuel(bool),
1142    ActivateBeacon {
1143        typ: BeaconType,
1144        system: BeaconSystem,
1145        name: Option<String>,
1146        callsign: String,
1147        frequency: i64,
1148    },
1149    DeactivateBeacon,
1150    ActivateICLS {
1151        channel: i64,
1152        unit: UnitId,
1153        name: Option<String>,
1154    },
1155    DeactivateICLS,
1156    EPLRS {
1157        enable: bool,
1158        group: Option<GroupId>,
1159    },
1160    Start,
1161    TransmitMessage {
1162        duration: Option<Time>,
1163        subtitle: Option<String>,
1164        looping: Option<bool>,
1165        file: String,
1166    },
1167    StopTransmission,
1168    Smoke(bool),
1169    ActivateLink4 {
1170        unit: UnitId,
1171        frequency: i64,
1172        name: Option<String>,
1173    },
1174    DeactivateLink4,
1175    ActivateACLS {
1176        unit: UnitId,
1177        name: Option<String>,
1178    },
1179    DeactivateACLS,
1180    LoadingShip {
1181        cargo: i64,
1182        unit: UnitId,
1183    },
1184}
1185
1186impl<'lua> IntoLua<'lua> for Command {
1187    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
1188        let root = lua.create_table()?;
1189        let params = lua.create_table()?;
1190        match self {
1191            Self::Script(s) => {
1192                root.raw_set("id", "Script")?;
1193                params.raw_set("command", s)?;
1194            }
1195            Self::SetCallsign { callname, number } => {
1196                root.raw_set("id", "SetCallsign")?;
1197                params.raw_set("callname", callname)?;
1198                params.raw_set("number", number)?;
1199            }
1200            Self::SetFrequency {
1201                frequency,
1202                modulation,
1203                power,
1204            } => {
1205                root.raw_set("id", "SetFrequency")?;
1206                params.raw_set("frequency", frequency)?;
1207                params.raw_set("modulation", modulation)?;
1208                params.raw_set("power", power)?;
1209            }
1210            Self::SetFrequencyForUnit {
1211                frequency,
1212                modulation,
1213                power,
1214                unit,
1215            } => {
1216                root.raw_set("id", "SetFrequencyForUnit")?;
1217                params.raw_set("frequency", frequency)?;
1218                params.raw_set("modulation", modulation)?;
1219                params.raw_set("power", power)?;
1220                params.raw_set("unitId", unit)?;
1221            }
1222            Self::SwitchWaypoint {
1223                from_waypoint,
1224                to_waypoint,
1225            } => {
1226                root.raw_set("id", "SwitchWaypoint")?;
1227                params.raw_set("fromWaypointIndex", from_waypoint)?;
1228                params.raw_set("goToWaypointIndex", to_waypoint)?;
1229            }
1230            Self::StopRoute(stop) => {
1231                root.raw_set("id", "StopRoute")?;
1232                params.raw_set("value", stop)?;
1233            }
1234            Self::SwitchAction(action) => {
1235                root.raw_set("id", "SwitchAction")?;
1236                params.raw_set("actionIndex", action)?;
1237            }
1238            Self::SetInvisible(invisible) => {
1239                root.raw_set("id", "SetInvisible")?;
1240                params.raw_set("value", invisible)?;
1241            }
1242            Self::SetImmortal(immortal) => {
1243                root.raw_set("id", "SetImmortal")?;
1244                params.raw_set("value", immortal)?;
1245            }
1246            Self::SetUnlimitedFuel(unlimited_fuel) => {
1247                root.raw_set("id", "SetUnlimitedFuel")?;
1248                params.raw_set("value", unlimited_fuel)?;
1249            }
1250            Self::ActivateBeacon {
1251                typ,
1252                system,
1253                name,
1254                callsign,
1255                frequency,
1256            } => {
1257                root.raw_set("id", "ActivateBeacon")?;
1258                params.raw_set("type", typ)?;
1259                params.raw_set("system", system)?;
1260                params.raw_set("callsign", callsign)?;
1261                params.raw_set("frequency", frequency)?;
1262                if let Some(name) = name {
1263                    params.raw_set("name", name)?;
1264                }
1265            }
1266            Self::DeactivateBeacon => root.raw_set("id", "DeactivateBeacon")?,
1267            Self::ActivateICLS {
1268                channel,
1269                unit,
1270                name,
1271            } => {
1272                root.raw_set("id", "ActivateICLS")?;
1273                params.raw_set("type", 131584)?;
1274                params.raw_set("channel", channel)?;
1275                params.raw_set("unitId", unit)?;
1276                if let Some(name) = name {
1277                    params.raw_set("name", name)?;
1278                }
1279            }
1280            Self::DeactivateICLS => root.raw_set("id", "DeactivateICLS")?,
1281            Self::EPLRS { enable, group } => {
1282                root.raw_set("id", "EPLRS")?;
1283                params.raw_set("value", enable)?;
1284                if let Some(group) = group {
1285                    params.raw_set("groupId", group)?;
1286                }
1287            }
1288            Self::Start => root.raw_set("id", "Start")?,
1289            Self::TransmitMessage {
1290                duration,
1291                subtitle,
1292                looping,
1293                file,
1294            } => {
1295                root.raw_set("id", "TransmitMessage")?;
1296                params.raw_set("file", file)?;
1297                if let Some(d) = duration {
1298                    params.raw_set("duration", d)?;
1299                }
1300                if let Some(s) = subtitle {
1301                    params.raw_set("subtitle", s)?;
1302                }
1303                if let Some(l) = looping {
1304                    params.raw_set("loop", l)?;
1305                }
1306            }
1307            Self::StopTransmission => root.raw_set("id", "stopTransmission")?,
1308            Self::Smoke(on) => {
1309                root.raw_set("id", "SMOKE_ON_OFF")?;
1310                params.raw_set("value", on)?
1311            }
1312            Self::ActivateLink4 {
1313                unit,
1314                frequency,
1315                name,
1316            } => {
1317                root.raw_set("id", "ActivateLink4")?;
1318                params.raw_set("unitId", unit)?;
1319                params.raw_set("frequency", frequency)?;
1320                if let Some(name) = name {
1321                    params.raw_set("name", name)?;
1322                }
1323            }
1324            Self::DeactivateLink4 => root.raw_set("id", "DeactivateLink4")?,
1325            Self::ActivateACLS { unit, name } => {
1326                root.raw_set("id", "ActivateACLS")?;
1327                params.raw_set("unitId", unit)?;
1328                if let Some(name) = name {
1329                    params.raw_set("name", name)?;
1330                }
1331            }
1332            Self::DeactivateACLS => root.raw_set("id", "DeactivateACLS")?,
1333            Self::LoadingShip { cargo, unit } => {
1334                root.raw_set("id", "LoadingShip")?;
1335                params.raw_set("cargo", cargo)?;
1336                params.raw_set("unitId", unit)?;
1337            }
1338        }
1339        root.raw_set("params", params)?;
1340        Ok(Value::Table(root))
1341    }
1342}
1343
1344impl<'lua> FromLua<'lua> for Command {
1345    fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
1346        let root: LuaTable = FromLua::from_lua(value, lua)?;
1347        let params: LuaTable = root.raw_get("params")?;
1348        match root.raw_get::<_, String>("id")?.as_str() {
1349            "Script" => Ok(Self::Script(params.raw_get("command")?)),
1350            "SetCallsign" => Ok(Self::SetCallsign {
1351                callname: params.raw_get("callname")?,
1352                number: params.raw_get("number")?,
1353            }),
1354            "SetFrequency" => Ok(Self::SetFrequency {
1355                frequency: params.raw_get("frequency")?,
1356                modulation: params.raw_get("modulation")?,
1357                power: params.raw_get("power")?,
1358            }),
1359            "SetFrequencyForUnit" => Ok(Self::SetFrequencyForUnit {
1360                frequency: params.raw_get("frequency")?,
1361                modulation: params.raw_get("modulation")?,
1362                power: params.raw_get("power")?,
1363                unit: params.raw_get("unitId")?,
1364            }),
1365            "SwitchWaypoint" => Ok(Self::SwitchWaypoint {
1366                from_waypoint: params.raw_get("fromWaypointIndex")?,
1367                to_waypoint: params.raw_get("goToWaypointIndex")?,
1368            }),
1369            "StopRoute" => Ok(Self::StopRoute(params.raw_get("value")?)),
1370            "SwitchAction" => Ok(Self::SwitchAction(params.raw_get("actionIndex")?)),
1371            "SetInvisible" => Ok(Self::SetInvisible(params.raw_get("value")?)),
1372            "SetImmortal" => Ok(Self::SetImmortal(params.raw_get("value")?)),
1373            "SetUnlimitedFuel" => Ok(Self::SetUnlimitedFuel(params.raw_get("value")?)),
1374            "ActivateBeacon" => Ok(Self::ActivateBeacon {
1375                typ: params.raw_get("type")?,
1376                system: params.raw_get("system")?,
1377                name: params.raw_get("name")?,
1378                callsign: params.raw_get("callsign")?,
1379                frequency: params.raw_get("frequency")?,
1380            }),
1381            "DeactivateBeacon" => Ok(Self::DeactivateBeacon),
1382            "DeactivateICLS" => Ok(Self::DeactivateACLS),
1383            "EPLRS" => Ok(Self::EPLRS {
1384                enable: params.raw_get("value")?,
1385                group: params.raw_get("groupId")?,
1386            }),
1387            "Start" => Ok(Self::Start),
1388            "TransmitMessage" => Ok(Self::TransmitMessage {
1389                duration: params.raw_get("duration")?,
1390                subtitle: params.raw_get("subtitle")?,
1391                looping: params.raw_get("loop")?,
1392                file: params.raw_get("file")?,
1393            }),
1394            "stopTransmission" => Ok(Self::StopTransmission),
1395            "ActivateLink4" => Ok(Self::ActivateLink4 {
1396                unit: params.raw_get("unitId")?,
1397                frequency: params.raw_get("frequency")?,
1398                name: params.raw_get("name")?,
1399            }),
1400            "DeactivateLink4" => Ok(Self::DeactivateLink4),
1401            "DeactivateACLS" => Ok(Self::DeactivateACLS),
1402            "ActivateACLS" => Ok(Self::ActivateACLS {
1403                unit: params.raw_get("unitId")?,
1404                name: params.raw_get("name")?,
1405            }),
1406            "LoadingShip" => Ok(Self::LoadingShip {
1407                cargo: params.raw_get("cargo")?,
1408                unit: params.raw_get("unitId")?,
1409            }),
1410            x => Err(err(&format_compact!("unknown {x}"))),
1411        }
1412    }
1413}
1414
1415simple_enum!(AirRoe, u8, [
1416    OpenFire => 2,
1417    OpenFireWeaponFree => 1,
1418    ReturnFire => 3,
1419    WeaponFree => 0,
1420    WeaponHold => 4
1421]);
1422
1423simple_enum!(AirReactionToThreat, u8, [
1424    NoReaction => 0,
1425    PassiveDefence => 1,
1426    EvadeFire => 2,
1427    BypassAndEscape => 3,
1428    AllowAbortMission => 4
1429]);
1430
1431simple_enum!(AirEcmUsing, u8, [
1432    AlwaysUse => 3,
1433    NeverUse => 0,
1434    UseIfDetectedLockByRadar => 2,
1435    UseIfOnlyLockByRadar => 1
1436]);
1437
1438simple_enum!(AirFlareUsing, u8, [
1439    AgainstFiredMissile => 1,
1440    Never => 0,
1441    WhenFlyingInSamWez => 2,
1442    WhenFlyingNearEnemies => 3
1443]);
1444
1445string_enum!(VehicleFormation, u8, [
1446    Cone => "Cone",
1447    Diamond => "Diamond",
1448    EchelonLeft => "EchelonL",
1449    EchelonRight => "EchelonR",
1450    OffRoad => "Off Road",
1451    OnRoad => "On Road",
1452    Rank => "Rank",
1453    Vee => "Vee"
1454]);
1455
1456simple_enum!(AirMissileAttack, u8, [
1457    HalfwayRmaxNez => 2,
1458    MaxRange => 0,
1459    NezRange => 1,
1460    RandomRange => 4,
1461    TargetThreatEst => 3
1462]);
1463
1464simple_enum!(AirRadarUsing, u8, [
1465    ForAttackOnly => 1,
1466    ForContinuousSearch => 3,
1467    ForSearchIfRequired => 2,
1468    Never => 0
1469]);
1470
1471#[derive(Debug, Clone, Serialize)]
1472pub enum AirOption<'lua> {
1473    EcmUsing(AirEcmUsing),
1474    FlareUsing(AirFlareUsing),
1475    ForcedAttack(bool),
1476    Formation(VehicleFormation),
1477    JettTanksIfEmpty(bool),
1478    MissileAttack(AirMissileAttack),
1479    OptionRadioUsageContact(Attributes<'lua>),
1480    OptionRadioUsageEngage(Attributes<'lua>),
1481    OptionRadioUsageKill(Attributes<'lua>),
1482    ProhibitAA(bool),
1483    ProhibitAB(bool),
1484    ProhibitAG(bool),
1485    ProhibitJett(bool),
1486    ProhibitWPPassReport(bool),
1487    RadarUsing(AirRadarUsing),
1488    ReactionOnThreat(AirReactionToThreat),
1489    Roe(AirRoe),
1490    RtbOnBingo(bool),
1491    RtbOnOutOfAmmo(bool),
1492    Silence(bool),
1493    AllowFormationSideSwap(bool),
1494}
1495
1496impl<'lua> IntoLua<'lua> for AirOption<'lua> {
1497    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
1498        match self {
1499            Self::EcmUsing(v) => v.into_lua(lua),
1500            Self::FlareUsing(v) => v.into_lua(lua),
1501            Self::ForcedAttack(v) => v.into_lua(lua),
1502            Self::Formation(v) => v.into_lua(lua),
1503            Self::JettTanksIfEmpty(v) => v.into_lua(lua),
1504            Self::MissileAttack(v) => v.into_lua(lua),
1505            Self::OptionRadioUsageContact(v) => v.into_lua(lua),
1506            Self::OptionRadioUsageEngage(v) => v.into_lua(lua),
1507            Self::OptionRadioUsageKill(v) => v.into_lua(lua),
1508            Self::ProhibitAA(v)
1509            | Self::ProhibitAB(v)
1510            | Self::ProhibitAG(v)
1511            | Self::ProhibitJett(v)
1512            | Self::ProhibitWPPassReport(v) => v.into_lua(lua),
1513            Self::RadarUsing(v) => v.into_lua(lua),
1514            Self::ReactionOnThreat(v) => v.into_lua(lua),
1515            Self::Roe(v) => v.into_lua(lua),
1516            Self::RtbOnBingo(v)
1517            | Self::RtbOnOutOfAmmo(v)
1518            | Self::Silence(v)
1519            | Self::AllowFormationSideSwap(v) => v.into_lua(lua),
1520        }
1521    }
1522}
1523
1524impl<'lua> AirOption<'lua> {
1525    fn tag(&self) -> u8 {
1526        match self {
1527            Self::EcmUsing(_) => 13,
1528            Self::FlareUsing(_) => 4,
1529            Self::ForcedAttack(_) => 26,
1530            Self::Formation(_) => 5,
1531            Self::JettTanksIfEmpty(_) => 25,
1532            Self::MissileAttack(_) => 18,
1533            Self::OptionRadioUsageContact(_) => 21,
1534            Self::OptionRadioUsageEngage(_) => 22,
1535            Self::OptionRadioUsageKill(_) => 23,
1536            Self::ProhibitAA(_) => 14,
1537            Self::ProhibitAB(_) => 16,
1538            Self::ProhibitAG(_) => 17,
1539            Self::ProhibitJett(_) => 15,
1540            Self::ProhibitWPPassReport(_) => 19,
1541            Self::RadarUsing(_) => 3,
1542            Self::ReactionOnThreat(_) => 1,
1543            Self::Roe(_) => 0,
1544            Self::RtbOnBingo(_) => 6,
1545            Self::RtbOnOutOfAmmo(_) => 10,
1546            Self::Silence(_) => 7,
1547            Self::AllowFormationSideSwap(_) => 35,
1548        }
1549    }
1550
1551    fn from_tag_val(lua: &'lua Lua, tag: u8, val: Value<'lua>) -> LuaResult<Self> {
1552        let attr_or_none = |val: Value<'lua>| match val {
1553            Value::String(s) if s.to_string_lossy().as_ref().starts_with("none") => {
1554                Attributes::new(lua).map_err(|e| err(&format_compact!("{}", e)))
1555            }
1556            v => FromLua::from_lua(v, lua),
1557        };
1558        match tag {
1559            13 => Ok(Self::EcmUsing(FromLua::from_lua(val, lua)?)),
1560            4 => Ok(Self::FlareUsing(FromLua::from_lua(val, lua)?)),
1561            26 => Ok(Self::ForcedAttack(FromLua::from_lua(val, lua)?)),
1562            5 => Ok(Self::Formation(FromLua::from_lua(val, lua)?)),
1563            25 => Ok(Self::JettTanksIfEmpty(FromLua::from_lua(val, lua)?)),
1564            18 => Ok(Self::MissileAttack(FromLua::from_lua(val, lua)?)),
1565            21 => Ok(Self::OptionRadioUsageContact(attr_or_none(val)?)),
1566            22 => Ok(Self::OptionRadioUsageEngage(attr_or_none(val)?)),
1567            23 => Ok(Self::OptionRadioUsageKill(attr_or_none(val)?)),
1568            14 => Ok(Self::ProhibitAA(FromLua::from_lua(val, lua)?)),
1569            16 => Ok(Self::ProhibitAB(FromLua::from_lua(val, lua)?)),
1570            17 => Ok(Self::ProhibitAG(FromLua::from_lua(val, lua)?)),
1571            15 => Ok(Self::ProhibitJett(FromLua::from_lua(val, lua)?)),
1572            19 => Ok(Self::ProhibitWPPassReport(FromLua::from_lua(val, lua)?)),
1573            3 => Ok(Self::RadarUsing(FromLua::from_lua(val, lua)?)),
1574            1 => Ok(Self::ReactionOnThreat(FromLua::from_lua(val, lua)?)),
1575            0 => Ok(Self::Roe(FromLua::from_lua(val, lua)?)),
1576            6 => Ok(Self::RtbOnBingo(FromLua::from_lua(val, lua)?)),
1577            10 => Ok(Self::RtbOnOutOfAmmo(FromLua::from_lua(val, lua)?)),
1578            7 => Ok(Self::Silence(FromLua::from_lua(val, lua)?)),
1579            35 => Ok(Self::AllowFormationSideSwap(FromLua::from_lua(val, lua)?)),
1580            e => Err(err(&format_compact!("invalid AirOption {e}"))),
1581        }
1582    }
1583}
1584
1585simple_enum!(AlarmState, u8, [
1586    Auto => 0,
1587    Green => 1,
1588    Red => 2
1589]);
1590
1591simple_enum!(GroundRoe, u8, [
1592    OpenFire => 2,
1593    ReturnFire => 3,
1594    WeaponHold => 4
1595]);
1596
1597#[derive(Debug, Clone, Serialize)]
1598pub enum GroundOption {
1599    AcEngagementRangeRestriction(u8),
1600    AlarmState(AlarmState),
1601    DisperseOnAttack(i64),
1602    EngageAirWeapons(bool),
1603    Formation(VehicleFormation),
1604    Roe(GroundRoe),
1605    AllowFormationSideSwap(bool),
1606}
1607
1608impl<'lua> IntoLua<'lua> for GroundOption {
1609    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
1610        match self {
1611            Self::AcEngagementRangeRestriction(v) => v.into_lua(lua),
1612            Self::AlarmState(v) => v.into_lua(lua),
1613            Self::DisperseOnAttack(v) => v.into_lua(lua),
1614            Self::EngageAirWeapons(v) => v.into_lua(lua),
1615            Self::Formation(v) => v.into_lua(lua),
1616            Self::Roe(v) => v.into_lua(lua),
1617            Self::AllowFormationSideSwap(v) => v.into_lua(lua),
1618        }
1619    }
1620}
1621
1622impl GroundOption {
1623    fn tag(&self) -> u8 {
1624        match self {
1625            Self::AcEngagementRangeRestriction(_) => 24,
1626            Self::AlarmState(_) => 9,
1627            Self::DisperseOnAttack(_) => 8,
1628            Self::EngageAirWeapons(_) => 20,
1629            Self::Formation(_) => 5,
1630            Self::Roe(_) => 0,
1631            Self::AllowFormationSideSwap(_) => 35,
1632        }
1633    }
1634
1635    fn from_tag_val(lua: &Lua, tag: u8, val: Value) -> LuaResult<Self> {
1636        match tag {
1637            0 => Ok(Self::Roe(FromLua::from_lua(val, lua)?)),
1638            5 => Ok(Self::Formation(FromLua::from_lua(val, lua)?)),
1639            8 => Ok(Self::DisperseOnAttack(FromLua::from_lua(val, lua)?)),
1640            9 => Ok(Self::AlarmState(FromLua::from_lua(val, lua)?)),
1641            20 => Ok(Self::EngageAirWeapons(FromLua::from_lua(val, lua)?)),
1642            24 => Ok(Self::AcEngagementRangeRestriction(FromLua::from_lua(
1643                val, lua,
1644            )?)),
1645            35 => Ok(Self::AllowFormationSideSwap(FromLua::from_lua(val, lua)?)),
1646            e => Err(err(&format_compact!("unknown GroundOption {e}"))),
1647        }
1648    }
1649}
1650
1651#[derive(Debug, Clone, Serialize)]
1652pub enum NavalOption {
1653    Roe(GroundRoe),
1654}
1655
1656impl<'lua> IntoLua<'lua> for NavalOption {
1657    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
1658        match self {
1659            Self::Roe(v) => v.into_lua(lua),
1660        }
1661    }
1662}
1663
1664impl NavalOption {
1665    fn tag(&self) -> u8 {
1666        match self {
1667            Self::Roe(_) => 0,
1668        }
1669    }
1670
1671    fn from_tag_val(lua: &Lua, tag: u8, val: Value) -> LuaResult<Self> {
1672        match tag {
1673            0 => Ok(Self::Roe(FromLua::from_lua(val, lua)?)),
1674            e => Err(err(&format_compact!("unkown NavalOption {e}"))),
1675        }
1676    }
1677}
1678
1679#[derive(Debug, Clone, Serialize)]
1680pub enum AiOption<'lua> {
1681    Air(AirOption<'lua>),
1682    Ground(GroundOption),
1683    Naval(NavalOption),
1684}
1685
1686impl<'lua> IntoLua<'lua> for AiOption<'lua> {
1687    fn into_lua(self, lua: &'lua Lua) -> LuaResult<Value<'lua>> {
1688        match self {
1689            Self::Air(v) => v.into_lua(lua),
1690            Self::Ground(v) => v.into_lua(lua),
1691            Self::Naval(v) => v.into_lua(lua),
1692        }
1693    }
1694}
1695
1696impl<'lua> AiOption<'lua> {
1697    fn tag(&self) -> u8 {
1698        match self {
1699            Self::Air(v) => v.tag(),
1700            Self::Ground(v) => v.tag(),
1701            Self::Naval(v) => v.tag(),
1702        }
1703    }
1704
1705    fn from_tag_val(lua: &'lua Lua, tag: u8, val: Value<'lua>) -> LuaResult<Self> {
1706        match AirOption::from_tag_val(lua, tag, val.clone()) {
1707            Ok(v) => Ok(Self::Air(v)),
1708            Err(ae) => match GroundOption::from_tag_val(lua, tag, val.clone()) {
1709                Ok(v) => Ok(Self::Ground(v)),
1710                Err(ge) => match NavalOption::from_tag_val(lua, tag, val) {
1711                    Ok(v) => Ok(Self::Naval(v)),
1712                    Err(ne) => Err(err(&format_compact!(
1713                        "unknown option, air: {ae:?} ground: {ge:?} naval: {ne:?}"
1714                    ))),
1715                },
1716            },
1717        }
1718    }
1719}
1720
1721bitflags_enum!(Detection, u8, [
1722    Dlink => 32,
1723    Irst => 8,
1724    Optic => 2,
1725    Radar => 4,
1726    Rwr => 16,
1727    Visual => 1
1728]);
1729
1730#[derive(Debug, Clone, Serialize)]
1731pub struct DetectedTargetInfo {
1732    pub is_detected: bool,
1733    pub is_visible: bool,
1734    pub last_time_seen: f64,
1735    pub type_known: bool,
1736    pub distance_known: bool,
1737    pub last_position: LuaVec3,
1738    pub last_velocity: LuaVec3,
1739}
1740
1741impl<'lua> FromLuaMulti<'lua> for DetectedTargetInfo {
1742    fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
1743        let is_detected = FromLua::from_lua(
1744            values
1745                .pop_front()
1746                .ok_or_else(|| cvt_err("DetectedTargetInfo:is_detected"))?,
1747            lua,
1748        )?;
1749        let is_visible = FromLua::from_lua(
1750            values
1751                .pop_front()
1752                .ok_or_else(|| cvt_err("DetectedTargetInfo:is_visible"))?,
1753            lua,
1754        )?;
1755        let last_time_seen = FromLua::from_lua(
1756            values
1757                .pop_front()
1758                .ok_or_else(|| cvt_err("DetectedTargetInfo:last_time_seen"))?,
1759            lua,
1760        )?;
1761        let type_known = FromLua::from_lua(
1762            values
1763                .pop_front()
1764                .ok_or_else(|| cvt_err("DetectedTargetInfo:type_known"))?,
1765            lua,
1766        )?;
1767        let distance_known = FromLua::from_lua(
1768            values
1769                .pop_front()
1770                .ok_or_else(|| cvt_err("DetectedTargetInfo:distance_known"))?,
1771            lua,
1772        )?;
1773        let last_position = FromLua::from_lua(
1774            values
1775                .pop_front()
1776                .ok_or_else(|| cvt_err("DetectedTargetInfo:last_position"))?,
1777            lua,
1778        )?;
1779        let last_velocity = FromLua::from_lua(
1780            values
1781                .pop_front()
1782                .ok_or_else(|| cvt_err("DetectedTargetInfo:last_velocity"))?,
1783            lua,
1784        )?;
1785        Ok(Self {
1786            is_detected,
1787            is_visible,
1788            last_time_seen,
1789            type_known,
1790            distance_known,
1791            last_position,
1792            last_velocity,
1793        })
1794    }
1795}
1796
1797#[derive(Debug, Clone, Serialize)]
1798pub struct DetectedTarget<'lua> {
1799    pub object: Object<'lua>,
1800    pub is_visible: bool,
1801    pub type_known: bool,
1802    pub distance_known: bool,
1803}
1804
1805impl<'lua> FromLua<'lua> for DetectedTarget<'lua> {
1806    fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> LuaResult<Self> {
1807        let tbl = as_tbl("DetectedTarget", None, value).map_err(lua_err)?;
1808        Ok(Self {
1809            object: tbl.raw_get("object")?,
1810            is_visible: tbl.raw_get("visible")?,
1811            type_known: tbl.raw_get("type")?,
1812            distance_known: tbl.raw_get("distance")?,
1813        })
1814    }
1815}
1816
1817string_enum!(AltitudeKind, u8, [
1818    Radio => "Radio",
1819    Baro => "Baro"
1820], [
1821    Radio => "RADIO",
1822    Baro => "BARO"
1823]);
1824
1825wrapped_table!(Controller, Some("Controller"));
1826
1827impl<'lua> Controller<'lua> {
1828    pub fn set_task(&self, task: Task) -> Result<()> {
1829        Ok(self.t.call_method("setTask", task)?)
1830    }
1831
1832    pub fn reset_task(&self) -> Result<()> {
1833        Ok(self.t.call_method("resetTask", ())?)
1834    }
1835
1836    pub fn push_task(&self, task: Task) -> Result<()> {
1837        Ok(self.t.call_method("pushTask", task)?)
1838    }
1839
1840    pub fn pop_task(&self) -> Result<()> {
1841        Ok(self.t.call_method("popTask", ())?)
1842    }
1843
1844    pub fn has_task(&self) -> Result<bool> {
1845        Ok(self.t.call_method("hasTask", ())?)
1846    }
1847
1848    pub fn set_command(&self, command: Command) -> Result<()> {
1849        Ok(self.t.call_method("setCommand", command)?)
1850    }
1851
1852    pub fn set_option(&self, option: AiOption<'lua>) -> Result<()> {
1853        Ok(self.t.call_method("setOption", (option.tag(), option))?)
1854    }
1855
1856    pub fn set_on_off(&self, on: bool) -> Result<()> {
1857        Ok(self.t.call_method("setOnOff", on)?)
1858    }
1859
1860    pub fn set_altitude(
1861        &self,
1862        altitude: f32,
1863        keep: bool,
1864        kind: Option<AltitudeKind>,
1865    ) -> Result<()> {
1866        Ok(match kind {
1867            None => self.t.call_method("setAltitude", (altitude, keep)),
1868            Some(kind) => self.t.call_method("setAltitude", (altitude, keep, kind)),
1869        }?)
1870    }
1871
1872    pub fn set_speed(&self, speed: f32, keep: bool) -> Result<()> {
1873        Ok(self.t.call_method("setSpeed", (speed, keep))?)
1874    }
1875
1876    pub fn know_target(&self, object: Object, typ: bool, distance: bool) -> Result<()> {
1877        Ok(self.t.call_method("knowTarget", (object, typ, distance))?)
1878    }
1879
1880    pub fn is_target_detected(
1881        &self,
1882        object: Object,
1883        methods: BitFlags<Detection>,
1884    ) -> Result<DetectedTargetInfo> {
1885        let mut args = Variadic::new();
1886        args.push(object.into_lua(self.lua)?);
1887        for method in methods {
1888            args.push(method.into_lua(self.lua)?);
1889        }
1890        Ok(self.t.call_method("isTargetDetected", args)?)
1891    }
1892
1893    pub fn get_detected_targets(
1894        &self,
1895        methods: BitFlags<Detection>,
1896    ) -> Result<Sequence<'lua, DetectedTarget<'lua>>> {
1897        let mut args = Variadic::new();
1898        for method in methods {
1899            args.push(method.into_lua(self.lua)?);
1900        }
1901        Ok(self.t.call_method("getDetectedTargets", args)?)
1902    }
1903}