1use 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>, pub expend: Option<WeaponExpend>,
102 pub direction: Option<f64>, 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>, 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>, }
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>, }
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>, 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 "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(¶ms)?;
756 }
757 Self::AttackUnit { unit, params: atp } => {
758 root.raw_set("id", "AttackUnit")?;
759 params.raw_set("unitId", unit)?;
760 atp.push_tbl(¶ms)?;
761 }
762 Self::Bombing { point, params: atp } => {
763 root.raw_set("id", "Bombing")?;
764 params.raw_set("point", point)?;
765 atp.push_tbl(¶ms)?;
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(¶ms)?;
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(¶ms)?;
786 }
787 Self::AttackMapObject { point, params: atp } => {
788 root.raw_set("id", "AttackMapObject")?;
789 params.raw_set("point", point)?;
790 atp.push_tbl(¶ms)?;
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(¶ms)?;
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(¶ms)?;
826 }
827 Self::FollowBigFormation(fp) => {
828 root.raw_set("id", "FollowBigFormation")?;
829 fp.push_tbl(¶ms)?;
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(¶ms)?;
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(¶ms)?;
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(¶ms)?;
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(¶ms)?;
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(¶ms)?;
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(¶ms)?;
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}