no clue what the current status is
This commit is contained in:
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
Cargo.lock
generated
Normal file → Executable file
0
Cargo.lock
generated
Normal file → Executable file
0
Cargo.toml
Normal file → Executable file
0
Cargo.toml
Normal file → Executable file
18
LICENSE
Normal file → Executable file
18
LICENSE
Normal file → Executable file
@@ -1,9 +1,9 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 Avii
|
Copyright (c) 2023 Avii
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|||||||
0
config.toml
Normal file → Executable file
0
config.toml
Normal file → Executable file
0
crates/dcs-grpc/Cargo.toml
Normal file → Executable file
0
crates/dcs-grpc/Cargo.toml
Normal file → Executable file
1320
crates/dcs-grpc/LICENSE
Normal file → Executable file
1320
crates/dcs-grpc/LICENSE
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
0
crates/dcs-grpc/build.rs
Normal file → Executable file
0
crates/dcs-grpc/build.rs
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/atmosphere/v0/atmosphere.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/atmosphere/v0/atmosphere.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/coalition/v0/coalition.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/coalition/v0/coalition.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/common/v0/common.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/common/v0/common.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/controller/v0/controller.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/controller/v0/controller.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/custom/v0/custom.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/custom/v0/custom.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/dcs.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/dcs.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/group/v0/group.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/group/v0/group.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/hook/v0/hook.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/hook/v0/hook.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/mission/v0/mission.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/mission/v0/mission.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/net/v0/net.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/net/v0/net.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/srs/v0/srs.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/srs/v0/srs.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/timer/v0/timer.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/timer/v0/timer.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/trigger/v0/trigger.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/trigger/v0/trigger.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/unit/v0/unit.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/unit/v0/unit.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/world/v0/world.proto
Normal file → Executable file
0
crates/dcs-grpc/protos/dcs/world/v0/world.proto
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/atmosphere.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/atmosphere.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/coalition.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/coalition.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/common.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/common.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/controller.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/controller.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/custom.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/custom.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/group.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/group.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/hook.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/hook.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/mission.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/mission.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/mod.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/mod.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/net.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/net.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/srs.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/srs.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/timer.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/timer.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/trigger.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/trigger.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/unit.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/unit.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/utils.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/utils.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/world.rs
Normal file → Executable file
0
crates/dcs-grpc/src/dcs/world.rs
Normal file → Executable file
0
crates/dcs-grpc/src/lib.rs
Normal file → Executable file
0
crates/dcs-grpc/src/lib.rs
Normal file → Executable file
0
crates/guardian_commands/Cargo.toml
Normal file → Executable file
0
crates/guardian_commands/Cargo.toml
Normal file → Executable file
902
crates/guardian_commands/src/call.rs
Normal file → Executable file
902
crates/guardian_commands/src/call.rs
Normal file → Executable file
@@ -1,451 +1,451 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{tag, tag_no_case};
|
use nom::bytes::complete::{tag, tag_no_case};
|
||||||
use nom::character::complete::{alpha1, alphanumeric1, char, multispace0};
|
use nom::character::complete::{alpha1, alphanumeric1, char, multispace0};
|
||||||
use nom::combinator::{map, opt, recognize};
|
use nom::combinator::{map, opt, recognize};
|
||||||
use nom::error::ErrorKind;
|
use nom::error::ErrorKind;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
use nom::{Err as NomError, IResult};
|
use nom::{Err as NomError, IResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Awacs {
|
pub enum Awacs {
|
||||||
Overlord,
|
Overlord,
|
||||||
Magic,
|
Magic,
|
||||||
Wizard,
|
Wizard,
|
||||||
Focus,
|
Focus,
|
||||||
Darkstar,
|
Darkstar,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Receiver {
|
pub enum Receiver {
|
||||||
Awacs(Awacs),
|
Awacs(Awacs),
|
||||||
Atc(String), //airfield name
|
Atc(String), //airfield name
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Receiver {
|
impl Display for Receiver {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Receiver::Awacs(Awacs::Overlord) => write!(f, "Overlord"),
|
Receiver::Awacs(Awacs::Overlord) => write!(f, "Overlord"),
|
||||||
Receiver::Awacs(Awacs::Magic) => write!(f, "Magic"),
|
Receiver::Awacs(Awacs::Magic) => write!(f, "Magic"),
|
||||||
Receiver::Awacs(Awacs::Wizard) => write!(f, "Wizard"),
|
Receiver::Awacs(Awacs::Wizard) => write!(f, "Wizard"),
|
||||||
Receiver::Awacs(Awacs::Focus) => write!(f, "Focus"),
|
Receiver::Awacs(Awacs::Focus) => write!(f, "Focus"),
|
||||||
Receiver::Awacs(Awacs::Darkstar) => write!(f, "Darkstar"),
|
Receiver::Awacs(Awacs::Darkstar) => write!(f, "Darkstar"),
|
||||||
Receiver::Atc(airfield) => write!(f, "{}", airfield.to_owned()),
|
Receiver::Atc(airfield) => write!(f, "{}", airfield.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl ToString for Receiver {
|
// impl ToString for Receiver {
|
||||||
// fn to_string(&self) -> String {
|
// fn to_string(&self) -> String {
|
||||||
// match self {
|
// match self {
|
||||||
// Receiver::Awacs(Awacs::Overlord) => "Overlord".to_string(),
|
// Receiver::Awacs(Awacs::Overlord) => "Overlord".to_string(),
|
||||||
// Receiver::Awacs(Awacs::Darkstar) => "Darkstar".to_string(),
|
// Receiver::Awacs(Awacs::Darkstar) => "Darkstar".to_string(),
|
||||||
// Receiver::Atc(airfield) => airfield.to_owned(),
|
// Receiver::Atc(airfield) => airfield.to_owned(),
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn parse_receiver(input: &str) -> IResult<&str, Receiver> {
|
fn parse_receiver(input: &str) -> IResult<&str, Receiver> {
|
||||||
alt((
|
alt((
|
||||||
map(tag_no_case("overlord"), |_| {
|
map(tag_no_case("overlord"), |_| {
|
||||||
Receiver::Awacs(Awacs::Overlord)
|
Receiver::Awacs(Awacs::Overlord)
|
||||||
}),
|
}),
|
||||||
map(tag_no_case("magic"), |_| Receiver::Awacs(Awacs::Magic)),
|
map(tag_no_case("magic"), |_| Receiver::Awacs(Awacs::Magic)),
|
||||||
map(tag_no_case("wizard"), |_| Receiver::Awacs(Awacs::Wizard)),
|
map(tag_no_case("wizard"), |_| Receiver::Awacs(Awacs::Wizard)),
|
||||||
map(tag_no_case("focus"), |_| Receiver::Awacs(Awacs::Focus)),
|
map(tag_no_case("focus"), |_| Receiver::Awacs(Awacs::Focus)),
|
||||||
map(tag_no_case("darkstar"), |_| {
|
map(tag_no_case("darkstar"), |_| {
|
||||||
Receiver::Awacs(Awacs::Darkstar)
|
Receiver::Awacs(Awacs::Darkstar)
|
||||||
}),
|
}),
|
||||||
map(alphanumeric1, |e: &str| Receiver::Atc(e.to_owned())), // anything else = airfield? airfields _can_ contain spaces though.. soo w... hmm... parsing that is a bitch
|
map(alphanumeric1, |e: &str| Receiver::Atc(e.to_owned())), // anything else = airfield? airfields _can_ contain spaces though.. soo w... hmm... parsing that is a bitch
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Squadron {
|
pub enum Squadron {
|
||||||
Enfield,
|
Enfield,
|
||||||
Springfield,
|
Springfield,
|
||||||
Uzi,
|
Uzi,
|
||||||
Colt,
|
Colt,
|
||||||
Dodge,
|
Dodge,
|
||||||
Ford,
|
Ford,
|
||||||
Chevy,
|
Chevy,
|
||||||
Pontiac,
|
Pontiac,
|
||||||
Viper,
|
Viper,
|
||||||
Venom,
|
Venom,
|
||||||
Lobo,
|
Lobo,
|
||||||
Cowboy,
|
Cowboy,
|
||||||
Python,
|
Python,
|
||||||
Rattler,
|
Rattler,
|
||||||
Panther,
|
Panther,
|
||||||
Wolf,
|
Wolf,
|
||||||
Weasel,
|
Weasel,
|
||||||
Wild,
|
Wild,
|
||||||
Ninja,
|
Ninja,
|
||||||
Jedi,
|
Jedi,
|
||||||
Hawg,
|
Hawg,
|
||||||
Boar,
|
Boar,
|
||||||
Pig,
|
Pig,
|
||||||
Tusk,
|
Tusk,
|
||||||
ArmyAir,
|
ArmyAir,
|
||||||
Apache,
|
Apache,
|
||||||
Crow,
|
Crow,
|
||||||
Chaos,
|
Chaos,
|
||||||
Sioux,
|
Sioux,
|
||||||
Gatling,
|
Gatling,
|
||||||
Gunslinger,
|
Gunslinger,
|
||||||
Hammerhead,
|
Hammerhead,
|
||||||
Bootleg,
|
Bootleg,
|
||||||
Palehorse,
|
Palehorse,
|
||||||
Carnivore,
|
Carnivore,
|
||||||
Saber,
|
Saber,
|
||||||
Hornet,
|
Hornet,
|
||||||
Squid,
|
Squid,
|
||||||
Ragin,
|
Ragin,
|
||||||
Roman,
|
Roman,
|
||||||
Sting,
|
Sting,
|
||||||
Jury,
|
Jury,
|
||||||
Joker,
|
Joker,
|
||||||
Ram,
|
Ram,
|
||||||
Hawk,
|
Hawk,
|
||||||
Devil,
|
Devil,
|
||||||
Check,
|
Check,
|
||||||
Snake,
|
Snake,
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Squadron {
|
impl ToString for Squadron {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Squadron::Enfield => "Enfield".to_string(),
|
Squadron::Enfield => "Enfield".to_string(),
|
||||||
Squadron::Springfield => "Springfield".to_string(),
|
Squadron::Springfield => "Springfield".to_string(),
|
||||||
Squadron::Uzi => "Uzi".to_string(),
|
Squadron::Uzi => "Uzi".to_string(),
|
||||||
Squadron::Colt => "Colt".to_string(),
|
Squadron::Colt => "Colt".to_string(),
|
||||||
Squadron::Dodge => "Dodge".to_string(),
|
Squadron::Dodge => "Dodge".to_string(),
|
||||||
Squadron::Ford => "Ford".to_string(),
|
Squadron::Ford => "Ford".to_string(),
|
||||||
Squadron::Chevy => "Chevy".to_string(),
|
Squadron::Chevy => "Chevy".to_string(),
|
||||||
Squadron::Hornet => "Hornet".to_string(),
|
Squadron::Hornet => "Hornet".to_string(),
|
||||||
Squadron::Pontiac => "Pontiac".to_string(),
|
Squadron::Pontiac => "Pontiac".to_string(),
|
||||||
Squadron::Viper => "Viper".to_string(),
|
Squadron::Viper => "Viper".to_string(),
|
||||||
Squadron::Venom => "Venom".to_string(),
|
Squadron::Venom => "Venom".to_string(),
|
||||||
Squadron::Lobo => "Lobo".to_string(),
|
Squadron::Lobo => "Lobo".to_string(),
|
||||||
Squadron::Cowboy => "Cowboy".to_string(),
|
Squadron::Cowboy => "Cowboy".to_string(),
|
||||||
Squadron::Python => "Python".to_string(),
|
Squadron::Python => "Python".to_string(),
|
||||||
Squadron::Rattler => "Rattler".to_string(),
|
Squadron::Rattler => "Rattler".to_string(),
|
||||||
Squadron::Panther => "Panther".to_string(),
|
Squadron::Panther => "Panther".to_string(),
|
||||||
Squadron::Wolf => "Wolf".to_string(),
|
Squadron::Wolf => "Wolf".to_string(),
|
||||||
Squadron::Weasel => "Weasel".to_string(),
|
Squadron::Weasel => "Weasel".to_string(),
|
||||||
Squadron::Wild => "Wild".to_string(),
|
Squadron::Wild => "Wild".to_string(),
|
||||||
Squadron::Ninja => "Ninja".to_string(),
|
Squadron::Ninja => "Ninja".to_string(),
|
||||||
Squadron::Jedi => "Jedi".to_string(),
|
Squadron::Jedi => "Jedi".to_string(),
|
||||||
Squadron::Hawg => "Hawg".to_string(),
|
Squadron::Hawg => "Hawg".to_string(),
|
||||||
Squadron::Boar => "Boar".to_string(),
|
Squadron::Boar => "Boar".to_string(),
|
||||||
Squadron::Pig => "Pig".to_string(),
|
Squadron::Pig => "Pig".to_string(),
|
||||||
Squadron::Tusk => "Tusk".to_string(),
|
Squadron::Tusk => "Tusk".to_string(),
|
||||||
Squadron::ArmyAir => "ArmyAir".to_string(),
|
Squadron::ArmyAir => "ArmyAir".to_string(),
|
||||||
Squadron::Apache => "Apache".to_string(),
|
Squadron::Apache => "Apache".to_string(),
|
||||||
Squadron::Crow => "Crow".to_string(),
|
Squadron::Crow => "Crow".to_string(),
|
||||||
Squadron::Chaos => "Chaos".to_string(),
|
Squadron::Chaos => "Chaos".to_string(),
|
||||||
Squadron::Sioux => "Sioux".to_string(),
|
Squadron::Sioux => "Sioux".to_string(),
|
||||||
Squadron::Gatling => "Gatling".to_string(),
|
Squadron::Gatling => "Gatling".to_string(),
|
||||||
Squadron::Gunslinger => "Gunslinger".to_string(),
|
Squadron::Gunslinger => "Gunslinger".to_string(),
|
||||||
Squadron::Hammerhead => "Hammerhead".to_string(),
|
Squadron::Hammerhead => "Hammerhead".to_string(),
|
||||||
Squadron::Bootleg => "Bootleg".to_string(),
|
Squadron::Bootleg => "Bootleg".to_string(),
|
||||||
Squadron::Palehorse => "Palehorse".to_string(),
|
Squadron::Palehorse => "Palehorse".to_string(),
|
||||||
Squadron::Carnivore => "Carnivore".to_string(),
|
Squadron::Carnivore => "Carnivore".to_string(),
|
||||||
Squadron::Saber => "Saber".to_string(),
|
Squadron::Saber => "Saber".to_string(),
|
||||||
Squadron::Squid => "Squid".to_string(),
|
Squadron::Squid => "Squid".to_string(),
|
||||||
Squadron::Ragin => "Ragin".to_string(),
|
Squadron::Ragin => "Ragin".to_string(),
|
||||||
Squadron::Roman => "Roman".to_string(),
|
Squadron::Roman => "Roman".to_string(),
|
||||||
Squadron::Sting => "Sting".to_string(),
|
Squadron::Sting => "Sting".to_string(),
|
||||||
Squadron::Jury => "Jury".to_string(),
|
Squadron::Jury => "Jury".to_string(),
|
||||||
Squadron::Joker => "Joker".to_string(),
|
Squadron::Joker => "Joker".to_string(),
|
||||||
Squadron::Ram => "Ram".to_string(),
|
Squadron::Ram => "Ram".to_string(),
|
||||||
Squadron::Hawk => "Hawk".to_string(),
|
Squadron::Hawk => "Hawk".to_string(),
|
||||||
Squadron::Devil => "Devil".to_string(),
|
Squadron::Devil => "Devil".to_string(),
|
||||||
Squadron::Check => "Check".to_string(),
|
Squadron::Check => "Check".to_string(),
|
||||||
Squadron::Snake => "Snake".to_string(),
|
Squadron::Snake => "Snake".to_string(),
|
||||||
Squadron::Custom(c) => c.to_string(),
|
Squadron::Custom(c) => c.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Squadron {
|
impl std::str::FromStr for Squadron {
|
||||||
type Err = Box<dyn std::error::Error + Send + Sync>;
|
type Err = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"Enfield" => Ok(Squadron::Enfield),
|
"Enfield" => Ok(Squadron::Enfield),
|
||||||
"Springfield" => Ok(Squadron::Springfield),
|
"Springfield" => Ok(Squadron::Springfield),
|
||||||
"Uzi" => Ok(Squadron::Uzi),
|
"Uzi" => Ok(Squadron::Uzi),
|
||||||
"Colt" => Ok(Squadron::Colt),
|
"Colt" => Ok(Squadron::Colt),
|
||||||
"Dodge" => Ok(Squadron::Dodge),
|
"Dodge" => Ok(Squadron::Dodge),
|
||||||
"Ford" => Ok(Squadron::Ford),
|
"Ford" => Ok(Squadron::Ford),
|
||||||
"Chevy" => Ok(Squadron::Chevy),
|
"Chevy" => Ok(Squadron::Chevy),
|
||||||
"Hornet" => Ok(Squadron::Hornet),
|
"Hornet" => Ok(Squadron::Hornet),
|
||||||
"Pontiac" => Ok(Squadron::Pontiac),
|
"Pontiac" => Ok(Squadron::Pontiac),
|
||||||
"Viper" => Ok(Squadron::Viper),
|
"Viper" => Ok(Squadron::Viper),
|
||||||
"Venom" => Ok(Squadron::Venom),
|
"Venom" => Ok(Squadron::Venom),
|
||||||
"Lobo" => Ok(Squadron::Lobo),
|
"Lobo" => Ok(Squadron::Lobo),
|
||||||
"Cowboy" => Ok(Squadron::Cowboy),
|
"Cowboy" => Ok(Squadron::Cowboy),
|
||||||
"Python" => Ok(Squadron::Python),
|
"Python" => Ok(Squadron::Python),
|
||||||
"Rattler" => Ok(Squadron::Rattler),
|
"Rattler" => Ok(Squadron::Rattler),
|
||||||
"Panther" => Ok(Squadron::Panther),
|
"Panther" => Ok(Squadron::Panther),
|
||||||
"Wolf" => Ok(Squadron::Wolf),
|
"Wolf" => Ok(Squadron::Wolf),
|
||||||
"Weasel" => Ok(Squadron::Weasel),
|
"Weasel" => Ok(Squadron::Weasel),
|
||||||
"Wild" => Ok(Squadron::Wild),
|
"Wild" => Ok(Squadron::Wild),
|
||||||
"Ninja" => Ok(Squadron::Ninja),
|
"Ninja" => Ok(Squadron::Ninja),
|
||||||
"Jedi" => Ok(Squadron::Jedi),
|
"Jedi" => Ok(Squadron::Jedi),
|
||||||
"Hawg" => Ok(Squadron::Hawg),
|
"Hawg" => Ok(Squadron::Hawg),
|
||||||
"Boar" => Ok(Squadron::Boar),
|
"Boar" => Ok(Squadron::Boar),
|
||||||
"Pig" => Ok(Squadron::Pig),
|
"Pig" => Ok(Squadron::Pig),
|
||||||
"Tusk" => Ok(Squadron::Tusk),
|
"Tusk" => Ok(Squadron::Tusk),
|
||||||
"ArmyAir" => Ok(Squadron::ArmyAir),
|
"ArmyAir" => Ok(Squadron::ArmyAir),
|
||||||
"Apache" => Ok(Squadron::Apache),
|
"Apache" => Ok(Squadron::Apache),
|
||||||
"Crow" => Ok(Squadron::Crow),
|
"Crow" => Ok(Squadron::Crow),
|
||||||
"Chaos" => Ok(Squadron::Chaos),
|
"Chaos" => Ok(Squadron::Chaos),
|
||||||
"Sioux" => Ok(Squadron::Sioux),
|
"Sioux" => Ok(Squadron::Sioux),
|
||||||
"Gatling" => Ok(Squadron::Gatling),
|
"Gatling" => Ok(Squadron::Gatling),
|
||||||
"Gunslinger" => Ok(Squadron::Gunslinger),
|
"Gunslinger" => Ok(Squadron::Gunslinger),
|
||||||
"Hammerhead" => Ok(Squadron::Hammerhead),
|
"Hammerhead" => Ok(Squadron::Hammerhead),
|
||||||
"Bootleg" => Ok(Squadron::Bootleg),
|
"Bootleg" => Ok(Squadron::Bootleg),
|
||||||
"Palehorse" => Ok(Squadron::Palehorse),
|
"Palehorse" => Ok(Squadron::Palehorse),
|
||||||
"Carnivore" => Ok(Squadron::Carnivore),
|
"Carnivore" => Ok(Squadron::Carnivore),
|
||||||
"Saber" => Ok(Squadron::Saber),
|
"Saber" => Ok(Squadron::Saber),
|
||||||
"Squid" => Ok(Squadron::Squid),
|
"Squid" => Ok(Squadron::Squid),
|
||||||
"Ragin" => Ok(Squadron::Ragin),
|
"Ragin" => Ok(Squadron::Ragin),
|
||||||
"Roman" => Ok(Squadron::Roman),
|
"Roman" => Ok(Squadron::Roman),
|
||||||
"Sting" => Ok(Squadron::Sting),
|
"Sting" => Ok(Squadron::Sting),
|
||||||
"Jury" => Ok(Squadron::Jury),
|
"Jury" => Ok(Squadron::Jury),
|
||||||
"Joker" => Ok(Squadron::Joker),
|
"Joker" => Ok(Squadron::Joker),
|
||||||
"Ram" => Ok(Squadron::Ram),
|
"Ram" => Ok(Squadron::Ram),
|
||||||
"Hawk" => Ok(Squadron::Hawk),
|
"Hawk" => Ok(Squadron::Hawk),
|
||||||
"Devil" => Ok(Squadron::Devil),
|
"Devil" => Ok(Squadron::Devil),
|
||||||
"Check" => Ok(Squadron::Check),
|
"Check" => Ok(Squadron::Check),
|
||||||
"Snake" => Ok(Squadron::Snake),
|
"Snake" => Ok(Squadron::Snake),
|
||||||
c => Ok(Squadron::Custom(c.to_string())),
|
c => Ok(Squadron::Custom(c.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
pub squadron: Squadron,
|
pub squadron: Squadron,
|
||||||
pub group: u8,
|
pub group: u8,
|
||||||
pub unit: u8,
|
pub unit: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Element {
|
impl Display for Element {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} {} {}",
|
"{} {} {}",
|
||||||
self.squadron.to_string(),
|
self.squadron.to_string(),
|
||||||
self.group,
|
self.group,
|
||||||
self.unit
|
self.unit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_spaces_around_numbers(input: &str) -> String {
|
fn add_spaces_around_numbers(input: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut chars = input.chars().peekable();
|
let mut chars = input.chars().peekable();
|
||||||
|
|
||||||
while let Some(c) = chars.next() {
|
while let Some(c) = chars.next() {
|
||||||
result.push(c);
|
result.push(c);
|
||||||
|
|
||||||
if let Some(next_char) = chars.peek() {
|
if let Some(next_char) = chars.peek() {
|
||||||
if next_char.is_numeric() {
|
if next_char.is_numeric() {
|
||||||
result.push(' ');
|
result.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Element {
|
impl std::str::FromStr for Element {
|
||||||
type Err = Box<dyn std::error::Error + Send + Sync>;
|
type Err = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let d = add_spaces_around_numbers(s);
|
let d = add_spaces_around_numbers(s);
|
||||||
let mut d = d.split(' ');
|
let mut d = d.split(' ');
|
||||||
let squadron: Squadron = d.next().unwrap().parse()?;
|
let squadron: Squadron = d.next().unwrap().parse()?;
|
||||||
let group: u8 = d.next().unwrap().parse()?;
|
let group: u8 = d.next().unwrap().parse()?;
|
||||||
let unit: u8 = d.next().unwrap().parse()?;
|
let unit: u8 = d.next().unwrap().parse()?;
|
||||||
|
|
||||||
Ok(Element {
|
Ok(Element {
|
||||||
squadron,
|
squadron,
|
||||||
group,
|
group,
|
||||||
unit,
|
unit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_element(input: &str) -> IResult<&str, Element> {
|
fn parse_element(input: &str) -> IResult<&str, Element> {
|
||||||
let mut parser = tuple((
|
let mut parser = tuple((
|
||||||
alpha1, // Squadron name
|
alpha1, // Squadron name
|
||||||
opt(multispace0), // Optional whitespace
|
opt(multispace0), // Optional whitespace
|
||||||
opt(tuple((
|
opt(tuple((
|
||||||
// Optional group
|
// Optional group
|
||||||
opt(multispace0),
|
opt(multispace0),
|
||||||
parse_group_unit,
|
parse_group_unit,
|
||||||
opt(multispace0),
|
opt(multispace0),
|
||||||
))),
|
))),
|
||||||
opt(char('-')),
|
opt(char('-')),
|
||||||
opt(tuple((
|
opt(tuple((
|
||||||
// Optional unit
|
// Optional unit
|
||||||
opt(multispace0),
|
opt(multispace0),
|
||||||
parse_group_unit,
|
parse_group_unit,
|
||||||
opt(multispace0),
|
opt(multispace0),
|
||||||
))),
|
))),
|
||||||
));
|
));
|
||||||
|
|
||||||
let (remainder, (squadron_str, _, group_str, _, unit_str)) = parser(input)?;
|
let (remainder, (squadron_str, _, group_str, _, unit_str)) = parser(input)?;
|
||||||
|
|
||||||
let squadron = match squadron_str.to_lowercase().as_str() {
|
let squadron = match squadron_str.to_lowercase().as_str() {
|
||||||
"enfield" => Squadron::Enfield,
|
"enfield" => Squadron::Enfield,
|
||||||
"springfield" => Squadron::Springfield,
|
"springfield" => Squadron::Springfield,
|
||||||
"uzi" => Squadron::Uzi,
|
"uzi" => Squadron::Uzi,
|
||||||
"colt" => Squadron::Colt,
|
"colt" => Squadron::Colt,
|
||||||
"dodge" => Squadron::Dodge,
|
"dodge" => Squadron::Dodge,
|
||||||
"ford" => Squadron::Ford,
|
"ford" => Squadron::Ford,
|
||||||
"chevy" => Squadron::Chevy,
|
"chevy" => Squadron::Chevy,
|
||||||
"hornet" => Squadron::Hornet,
|
"hornet" => Squadron::Hornet,
|
||||||
"pontiac" => Squadron::Pontiac,
|
"pontiac" => Squadron::Pontiac,
|
||||||
"viper" => Squadron::Viper,
|
"viper" => Squadron::Viper,
|
||||||
"venom" => Squadron::Venom,
|
"venom" => Squadron::Venom,
|
||||||
"lobo" => Squadron::Lobo,
|
"lobo" => Squadron::Lobo,
|
||||||
"cowboy" => Squadron::Cowboy,
|
"cowboy" => Squadron::Cowboy,
|
||||||
"python" => Squadron::Python,
|
"python" => Squadron::Python,
|
||||||
"rattler" => Squadron::Rattler,
|
"rattler" => Squadron::Rattler,
|
||||||
"panther" => Squadron::Panther,
|
"panther" => Squadron::Panther,
|
||||||
"wolf" => Squadron::Wolf,
|
"wolf" => Squadron::Wolf,
|
||||||
"weasel" => Squadron::Weasel,
|
"weasel" => Squadron::Weasel,
|
||||||
"wild" => Squadron::Wild,
|
"wild" => Squadron::Wild,
|
||||||
"ninja" => Squadron::Ninja,
|
"ninja" => Squadron::Ninja,
|
||||||
"jedi" => Squadron::Jedi,
|
"jedi" => Squadron::Jedi,
|
||||||
"hawg" => Squadron::Hawg,
|
"hawg" => Squadron::Hawg,
|
||||||
"boar" => Squadron::Boar,
|
"boar" => Squadron::Boar,
|
||||||
"pig" => Squadron::Pig,
|
"pig" => Squadron::Pig,
|
||||||
"tusk" => Squadron::Tusk,
|
"tusk" => Squadron::Tusk,
|
||||||
"armyair" => Squadron::ArmyAir,
|
"armyair" => Squadron::ArmyAir,
|
||||||
"apache" => Squadron::Apache,
|
"apache" => Squadron::Apache,
|
||||||
"crow" => Squadron::Crow,
|
"crow" => Squadron::Crow,
|
||||||
"chaos" => Squadron::Chaos,
|
"chaos" => Squadron::Chaos,
|
||||||
"sioux" => Squadron::Sioux,
|
"sioux" => Squadron::Sioux,
|
||||||
"gatling" => Squadron::Gatling,
|
"gatling" => Squadron::Gatling,
|
||||||
"gunslinger" => Squadron::Gunslinger,
|
"gunslinger" => Squadron::Gunslinger,
|
||||||
"hammerhead" => Squadron::Hammerhead,
|
"hammerhead" => Squadron::Hammerhead,
|
||||||
"bootleg" => Squadron::Bootleg,
|
"bootleg" => Squadron::Bootleg,
|
||||||
"palehorse" => Squadron::Palehorse,
|
"palehorse" => Squadron::Palehorse,
|
||||||
"carnivore" => Squadron::Carnivore,
|
"carnivore" => Squadron::Carnivore,
|
||||||
"saber" => Squadron::Saber,
|
"saber" => Squadron::Saber,
|
||||||
"squid" => Squadron::Squid,
|
"squid" => Squadron::Squid,
|
||||||
"ragin" => Squadron::Ragin,
|
"ragin" => Squadron::Ragin,
|
||||||
"roman" => Squadron::Roman,
|
"roman" => Squadron::Roman,
|
||||||
"sting" => Squadron::Sting,
|
"sting" => Squadron::Sting,
|
||||||
"jury" => Squadron::Jury,
|
"jury" => Squadron::Jury,
|
||||||
"joker" => Squadron::Joker,
|
"joker" => Squadron::Joker,
|
||||||
"ram" => Squadron::Ram,
|
"ram" => Squadron::Ram,
|
||||||
"hawk" => Squadron::Hawk,
|
"hawk" => Squadron::Hawk,
|
||||||
"devil" => Squadron::Devil,
|
"devil" => Squadron::Devil,
|
||||||
"check" => Squadron::Check,
|
"check" => Squadron::Check,
|
||||||
"snake" => Squadron::Snake,
|
"snake" => Squadron::Snake,
|
||||||
c => Squadron::Custom(c.to_string()),
|
c => Squadron::Custom(c.to_string()),
|
||||||
};
|
};
|
||||||
// _ => {
|
// _ => {
|
||||||
// return Err(NomError::Error(nom::error::Error {
|
// return Err(NomError::Error(nom::error::Error {
|
||||||
// input: squadron_str,
|
// input: squadron_str,
|
||||||
// code: ErrorKind::Alpha,
|
// code: ErrorKind::Alpha,
|
||||||
// }))
|
// }))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let group = group_str.unwrap_or((None, 0, None)).1;
|
let group = group_str.unwrap_or((None, 0, None)).1;
|
||||||
let unit = unit_str.unwrap_or((None, 0, None)).1;
|
let unit = unit_str.unwrap_or((None, 0, None)).1;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remainder,
|
remainder,
|
||||||
Element {
|
Element {
|
||||||
squadron,
|
squadron,
|
||||||
group,
|
group,
|
||||||
unit,
|
unit,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_group_unit(input: &str) -> IResult<&str, u8> {
|
fn parse_group_unit(input: &str) -> IResult<&str, u8> {
|
||||||
let parse_digit = map(
|
let parse_digit = map(
|
||||||
recognize(alt((
|
recognize(alt((
|
||||||
char('0'),
|
char('0'),
|
||||||
char('1'),
|
char('1'),
|
||||||
char('2'),
|
char('2'),
|
||||||
char('3'),
|
char('3'),
|
||||||
char('4'),
|
char('4'),
|
||||||
char('5'),
|
char('5'),
|
||||||
char('6'),
|
char('6'),
|
||||||
char('7'),
|
char('7'),
|
||||||
char('8'),
|
char('8'),
|
||||||
char('9'),
|
char('9'),
|
||||||
))),
|
))),
|
||||||
|s: &str| s.parse::<u8>().unwrap(),
|
|s: &str| s.parse::<u8>().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let parse_word = alt((
|
let parse_word = alt((
|
||||||
map(tag("one"), |_| 1),
|
map(tag("one"), |_| 1),
|
||||||
map(tag("two"), |_| 2),
|
map(tag("two"), |_| 2),
|
||||||
map(tag("three"), |_| 3),
|
map(tag("three"), |_| 3),
|
||||||
map(tag("four"), |_| 4),
|
map(tag("four"), |_| 4),
|
||||||
map(tag("five"), |_| 5),
|
map(tag("five"), |_| 5),
|
||||||
map(tag("six"), |_| 6),
|
map(tag("six"), |_| 6),
|
||||||
map(tag("seven"), |_| 7),
|
map(tag("seven"), |_| 7),
|
||||||
map(tag("eight"), |_| 8),
|
map(tag("eight"), |_| 8),
|
||||||
map(tag("nine"), |_| 9),
|
map(tag("nine"), |_| 9),
|
||||||
));
|
));
|
||||||
|
|
||||||
alt((parse_digit, parse_word))(input)
|
alt((parse_digit, parse_word))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
// pub enum Unit {
|
// pub enum Unit {
|
||||||
// NauticalMiles,
|
// NauticalMiles,
|
||||||
// Kilometers,
|
// Kilometers,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// #[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
// pub enum Call {
|
// pub enum Call {
|
||||||
// RadioCheck(Awacs, Element),
|
// RadioCheck(Awacs, Element),
|
||||||
// Shopping(Awacs, Element),
|
// Shopping(Awacs, Element),
|
||||||
// LocateAirfield(Awacs, Element, String),
|
// LocateAirfield(Awacs, Element, String),
|
||||||
// LocateFriendly(Awacs, Element, Element),
|
// LocateFriendly(Awacs, Element, Element),
|
||||||
// Tripwire(Awacs, Element, Option<i32>, Unit),
|
// Tripwire(Awacs, Element, Option<i32>, Unit),
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Call {
|
pub struct Call {
|
||||||
pub receiver: Receiver,
|
pub receiver: Receiver,
|
||||||
pub element: Element,
|
pub element: Element,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_call(input: &str) -> IResult<&str, Call> {
|
pub fn parse_call(input: &str) -> IResult<&str, Call> {
|
||||||
let mut parser = tuple((parse_receiver, multispace0, parse_element));
|
let mut parser = tuple((parse_receiver, multispace0, parse_element));
|
||||||
|
|
||||||
let (command, (receiver, _, element)) = parser(input)?;
|
let (command, (receiver, _, element)) = parser(input)?;
|
||||||
|
|
||||||
if element.group == 0 || element.unit == 0 {
|
if element.group == 0 || element.unit == 0 {
|
||||||
return Err(NomError::Error(nom::error::Error {
|
return Err(NomError::Error(nom::error::Error {
|
||||||
input: command,
|
input: command,
|
||||||
code: ErrorKind::Digit,
|
code: ErrorKind::Digit,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Call {
|
Call {
|
||||||
receiver,
|
receiver,
|
||||||
element,
|
element,
|
||||||
command: command.to_string(),
|
command: command.to_string(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
1098
crates/guardian_commands/src/console.rs
Normal file → Executable file
1098
crates/guardian_commands/src/console.rs
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
0
crates/guardian_commands/src/lib.rs
Normal file → Executable file
0
crates/guardian_commands/src/lib.rs
Normal file → Executable file
388
crates/guardian_commands/src/parser.rs
Normal file → Executable file
388
crates/guardian_commands/src/parser.rs
Normal file → Executable file
@@ -1,194 +1,194 @@
|
|||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
error::{Error, ErrorKind, ParseError},
|
error::{Error, ErrorKind, ParseError},
|
||||||
sequence::delimited,
|
sequence::delimited,
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
// from https://github.com/getreu/parse-hyperlinks/blob/5af034d14aa72ffb9e705da13bf557a564b1bebf/parse-hyperlinks/src/lib.rs#L41
|
// from https://github.com/getreu/parse-hyperlinks/blob/5af034d14aa72ffb9e705da13bf557a564b1bebf/parse-hyperlinks/src/lib.rs#L41
|
||||||
fn take_until_unbalanced(
|
fn take_until_unbalanced(
|
||||||
opening_bracket: char,
|
opening_bracket: char,
|
||||||
closing_bracket: char,
|
closing_bracket: char,
|
||||||
) -> impl Fn(&str) -> IResult<&str, &str> {
|
) -> impl Fn(&str) -> IResult<&str, &str> {
|
||||||
move |i: &str| {
|
move |i: &str| {
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
let mut bracket_counter = 0;
|
let mut bracket_counter = 0;
|
||||||
while let Some(n) = &i[index..].find(&[opening_bracket, closing_bracket, '\\'][..]) {
|
while let Some(n) = &i[index..].find(&[opening_bracket, closing_bracket, '\\'][..]) {
|
||||||
index += n;
|
index += n;
|
||||||
let mut it = i[index..].chars();
|
let mut it = i[index..].chars();
|
||||||
match it.next() {
|
match it.next() {
|
||||||
Some('\\') => {
|
Some('\\') => {
|
||||||
// Skip the escape char `\`.
|
// Skip the escape char `\`.
|
||||||
index += '\\'.len_utf8();
|
index += '\\'.len_utf8();
|
||||||
// Skip also the following char.
|
// Skip also the following char.
|
||||||
if let Some(c) = it.next() {
|
if let Some(c) = it.next() {
|
||||||
index += c.len_utf8();
|
index += c.len_utf8();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(c) if c == opening_bracket => {
|
Some(c) if c == opening_bracket => {
|
||||||
bracket_counter += 1;
|
bracket_counter += 1;
|
||||||
index += opening_bracket.len_utf8();
|
index += opening_bracket.len_utf8();
|
||||||
}
|
}
|
||||||
Some(c) if c == closing_bracket => {
|
Some(c) if c == closing_bracket => {
|
||||||
// Closing bracket.
|
// Closing bracket.
|
||||||
bracket_counter -= 1;
|
bracket_counter -= 1;
|
||||||
index += closing_bracket.len_utf8();
|
index += closing_bracket.len_utf8();
|
||||||
}
|
}
|
||||||
// Can not happen.
|
// Can not happen.
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
// We found the unmatched closing bracket.
|
// We found the unmatched closing bracket.
|
||||||
if bracket_counter == -1 {
|
if bracket_counter == -1 {
|
||||||
// We do not consume it.
|
// We do not consume it.
|
||||||
index -= closing_bracket.len_utf8();
|
index -= closing_bracket.len_utf8();
|
||||||
return Ok((&i[index..], &i[0..index]));
|
return Ok((&i[index..], &i[0..index]));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if bracket_counter == 0 {
|
if bracket_counter == 0 {
|
||||||
Ok(("", i))
|
Ok(("", i))
|
||||||
} else {
|
} else {
|
||||||
Err(nom::Err::Error(Error::from_error_kind(
|
Err(nom::Err::Error(Error::from_error_kind(
|
||||||
i,
|
i,
|
||||||
ErrorKind::TakeUntil,
|
ErrorKind::TakeUntil,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split(input: &str) -> IResult<&str, &str> {
|
fn split(input: &str) -> IResult<&str, &str> {
|
||||||
let mut parser = delimited(tag("["), take_until_unbalanced('[', ']'), tag("]"));
|
let mut parser = delimited(tag("["), take_until_unbalanced('[', ']'), tag("]"));
|
||||||
parser(input)
|
parser(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_parse(input: &str, depth: usize) -> Vec<String> {
|
fn inner_parse(input: &str, depth: usize) -> Vec<String> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let start_idx = input.find('[').unwrap_or(input.len());
|
let start_idx = input.find('[').unwrap_or(input.len());
|
||||||
|
|
||||||
if start_idx > 0 && depth == 0 {
|
if start_idx > 0 && depth == 0 {
|
||||||
output.push(input[..start_idx].to_string());
|
output.push(input[..start_idx].to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if start_idx == input.len() {
|
if start_idx == input.len() {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = &input[start_idx..];
|
let input = &input[start_idx..];
|
||||||
|
|
||||||
let splitted = split(input).unwrap();
|
let splitted = split(input).unwrap();
|
||||||
|
|
||||||
let variants: Vec<String> = splitted
|
let variants: Vec<String> = splitted
|
||||||
.1
|
.1
|
||||||
.split('|')
|
.split('|')
|
||||||
.map(|v| v.to_string())
|
.map(|v| v.to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut temp = vec![];
|
let mut temp = vec![];
|
||||||
let mut temp_str = String::new();
|
let mut temp_str = String::new();
|
||||||
for v in &variants {
|
for v in &variants {
|
||||||
if v.contains('[') || v.contains(']') {
|
if v.contains('[') || v.contains(']') {
|
||||||
temp_str.push_str(v);
|
temp_str.push_str(v);
|
||||||
temp_str.push('|');
|
temp_str.push('|');
|
||||||
} else {
|
} else {
|
||||||
let len = temp_str.len();
|
let len = temp_str.len();
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
temp.push(temp_str[..len - 1].to_string());
|
temp.push(temp_str[..len - 1].to_string());
|
||||||
temp.push(v.to_string());
|
temp.push(v.to_string());
|
||||||
}
|
}
|
||||||
temp_str.clear();
|
temp_str.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let variants = if temp.is_empty() { variants } else { temp };
|
let variants = if temp.is_empty() { variants } else { temp };
|
||||||
|
|
||||||
// check if variants need expanding
|
// check if variants need expanding
|
||||||
let mut temp = vec![];
|
let mut temp = vec![];
|
||||||
for v in variants {
|
for v in variants {
|
||||||
if v.contains('[') {
|
if v.contains('[') {
|
||||||
let index = v.find('[').unwrap_or(0);
|
let index = v.find('[').unwrap_or(0);
|
||||||
let prepend = &v[..index];
|
let prepend = &v[..index];
|
||||||
|
|
||||||
let remainder = inner_parse(&v, depth + 1);
|
let remainder = inner_parse(&v, depth + 1);
|
||||||
for r in &remainder {
|
for r in &remainder {
|
||||||
let l = format!("{}{}", prepend, r);
|
let l = format!("{}{}", prepend, r);
|
||||||
temp.push(l);
|
temp.push(l);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
temp.push(v);
|
temp.push(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let remainder = splitted.0;
|
let remainder = splitted.0;
|
||||||
let backup = output.clone();
|
let backup = output.clone();
|
||||||
output.clear();
|
output.clear();
|
||||||
|
|
||||||
for variant in temp {
|
for variant in temp {
|
||||||
if remainder.contains('[') {
|
if remainder.contains('[') {
|
||||||
let index = remainder.find('[').unwrap_or(0);
|
let index = remainder.find('[').unwrap_or(0);
|
||||||
let prepend = &remainder[..index];
|
let prepend = &remainder[..index];
|
||||||
|
|
||||||
let r2 = inner_parse(remainder, depth + 1);
|
let r2 = inner_parse(remainder, depth + 1);
|
||||||
|
|
||||||
if !backup.is_empty() {
|
if !backup.is_empty() {
|
||||||
for o in &backup {
|
for o in &backup {
|
||||||
for r in &r2 {
|
for r in &r2 {
|
||||||
let l = format!("{}{}{}{}", o, variant, prepend, r);
|
let l = format!("{}{}{}{}", o, variant, prepend, r);
|
||||||
output.push(l);
|
output.push(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for r in &r2 {
|
for r in &r2 {
|
||||||
let l = format!("{}{}{}", variant, prepend, r);
|
let l = format!("{}{}{}", variant, prepend, r);
|
||||||
output.push(l);
|
output.push(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !backup.is_empty() {
|
} else if !backup.is_empty() {
|
||||||
for o in &backup {
|
for o in &backup {
|
||||||
output.push(format!("{}{}{}", o, variant, remainder));
|
output.push(format!("{}{}{}", o, variant, remainder));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output.push(format!("{}{}", variant, remainder));
|
output.push(format!("{}{}", variant, remainder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Vec<String> {
|
pub fn parse(input: &str) -> Vec<String> {
|
||||||
inner_parse(input, 0)
|
inner_parse(input, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_test() {
|
fn parse_test() {
|
||||||
// This works
|
// This works
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("[Hello|Goodbye], World!"),
|
parse("[Hello|Goodbye], World!"),
|
||||||
vec!["Hello, World!", "Goodbye, World!"]
|
vec!["Hello, World!", "Goodbye, World!"]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("[Hello|Goodbye], [World|Moon]!"),
|
parse("[Hello|Goodbye], [World|Moon]!"),
|
||||||
vec![
|
vec![
|
||||||
"Hello, World!",
|
"Hello, World!",
|
||||||
"Hello, Moon!",
|
"Hello, Moon!",
|
||||||
"Goodbye, World!",
|
"Goodbye, World!",
|
||||||
"Goodbye, Moon!",
|
"Goodbye, Moon!",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("[[Heading|Bearing] [to|for]|Where is] that"),
|
parse("[[Heading|Bearing] [to|for]|Where is] that"),
|
||||||
vec![
|
vec![
|
||||||
"Heading to that",
|
"Heading to that",
|
||||||
"Heading for that",
|
"Heading for that",
|
||||||
"Bearing to that",
|
"Bearing to that",
|
||||||
"Bearing for that",
|
"Bearing for that",
|
||||||
"Where is that",
|
"Where is that",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("set [warning|tripwire]"),
|
parse("set [warning|tripwire]"),
|
||||||
vec!["set warning", "set tripwire"]
|
vec!["set warning", "set tripwire"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
0
crates/guardian_core/Cargo.toml
Normal file → Executable file
0
crates/guardian_core/Cargo.toml
Normal file → Executable file
8
crates/guardian_core/src/components/awacs.rs
Normal file → Executable file
8
crates/guardian_core/src/components/awacs.rs
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Awacs;
|
pub struct Awacs;
|
||||||
|
|||||||
210
crates/guardian_core/src/components/callsign.rs
Normal file → Executable file
210
crates/guardian_core/src/components/callsign.rs
Normal file → Executable file
@@ -1,105 +1,105 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{component::Component, reflect::ReflectComponent},
|
ecs::{component::Component, reflect::ReflectComponent},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Reflect, Component, Clone)]
|
#[derive(Reflect, Component, Clone)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct Callsign {
|
pub struct Callsign {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
value: Cow<'static, str>,
|
value: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Callsign {
|
impl Default for Callsign {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Callsign::new("")
|
Callsign::new("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_spaces_around_numbers(input: &str) -> String {
|
fn add_spaces_around_numbers(input: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut chars = input.chars().peekable();
|
let mut chars = input.chars().peekable();
|
||||||
|
|
||||||
while let Some(c) = chars.next() {
|
while let Some(c) = chars.next() {
|
||||||
result.push(c);
|
result.push(c);
|
||||||
|
|
||||||
if let Some(next_char) = chars.peek() {
|
if let Some(next_char) = chars.peek() {
|
||||||
if next_char.is_numeric() {
|
if next_char.is_numeric() {
|
||||||
result.push(' ');
|
result.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Callsign {
|
impl Callsign {
|
||||||
/// Creates a new [`Callsign`] from any string-like type.
|
/// Creates a new [`Callsign`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
|
|
||||||
let mut value = Callsign { value, hash: 0 };
|
let mut value = Callsign { value, hash: 0 };
|
||||||
value.update_hash();
|
value.update_hash();
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_voice(&self) -> String {
|
pub fn to_voice(&self) -> String {
|
||||||
let value: String = self.value.to_string();
|
let value: String = self.value.to_string();
|
||||||
let value = value.replace(' ', "");
|
let value = value.replace(' ', "");
|
||||||
let value = value.replace('-', "");
|
let value = value.replace('-', "");
|
||||||
|
|
||||||
add_spaces_around_numbers(&value)
|
add_spaces_around_numbers(&value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's value.
|
/// Sets the entity's value.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
||||||
*self = Callsign::new(value);
|
*self = Callsign::new(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of the entity in place.
|
/// Updates the value of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the value was previously
|
/// This will allocate a new string if the value was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.value.to_mut());
|
f(self.value.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value of the entity as a `&str`.
|
/// Gets the value of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.value.hash(&mut hasher);
|
self.value.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Callsign {
|
impl std::fmt::Display for Callsign {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.value, f)
|
std::fmt::Display::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Callsign {
|
impl std::fmt::Debug for Callsign {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.value, f)
|
std::fmt::Debug::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
crates/guardian_core/src/components/group.rs
Normal file → Executable file
14
crates/guardian_core/src/components/group.rs
Normal file → Executable file
@@ -1,7 +1,7 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub unit: u32,
|
pub unit: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
188
crates/guardian_core/src/components/grpc_base_url.rs
Normal file → Executable file
188
crates/guardian_core/src/components/grpc_base_url.rs
Normal file → Executable file
@@ -1,94 +1,94 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{reflect::ReflectResource, system::Resource},
|
ecs::{reflect::ReflectResource, system::Resource},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Reflect, Resource, Clone, Deserialize, Serialize)]
|
#[derive(Reflect, Resource, Clone, Deserialize, Serialize)]
|
||||||
#[reflect(Resource, Default, Debug)]
|
#[reflect(Resource, Default, Debug)]
|
||||||
pub struct GrpcBaseUrl {
|
pub struct GrpcBaseUrl {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
hash: u64,
|
hash: u64,
|
||||||
/// The URL of the DCS-gRPC Server
|
/// The URL of the DCS-gRPC Server
|
||||||
url: Cow<'static, str>,
|
url: Cow<'static, str>,
|
||||||
/// The poll rate for updating the mission situation in secononds.
|
/// The poll rate for updating the mission situation in secononds.
|
||||||
/// Lower values might cause noticeable ingame lag spikes
|
/// Lower values might cause noticeable ingame lag spikes
|
||||||
poll_rate: Option<u32>,
|
poll_rate: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GrpcBaseUrl {
|
impl Default for GrpcBaseUrl {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
GrpcBaseUrl::new("http://127.0.0.1:50051/", Some(10))
|
GrpcBaseUrl::new("http://127.0.0.1:50051/", Some(10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GrpcBaseUrl {
|
impl GrpcBaseUrl {
|
||||||
/// Creates a new [`GrpcBaseUrl`] from any string-like type.
|
/// Creates a new [`GrpcBaseUrl`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(url: impl Into<Cow<'static, str>>, poll_rate: Option<u32>) -> Self {
|
pub fn new(url: impl Into<Cow<'static, str>>, poll_rate: Option<u32>) -> Self {
|
||||||
let url = url.into();
|
let url = url.into();
|
||||||
let mut url = GrpcBaseUrl {
|
let mut url = GrpcBaseUrl {
|
||||||
url,
|
url,
|
||||||
hash: 0,
|
hash: 0,
|
||||||
poll_rate,
|
poll_rate,
|
||||||
};
|
};
|
||||||
url.update_hash();
|
url.update_hash();
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's url.
|
/// Sets the entity's url.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, url: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, url: impl Into<Cow<'static, str>>) {
|
||||||
*self = GrpcBaseUrl::new(url, self.poll_rate);
|
*self = GrpcBaseUrl::new(url, self.poll_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the url of the entity in place.
|
/// Updates the url of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the url was previously
|
/// This will allocate a new string if the url was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.url.to_mut());
|
f(self.url.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the url of the entity as a `&str`.
|
/// Gets the url of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.url
|
&self.url
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn poll_rate(&self) -> Option<u32> {
|
pub fn poll_rate(&self) -> Option<u32> {
|
||||||
self.poll_rate
|
self.poll_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.url.hash(&mut hasher);
|
self.url.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for GrpcBaseUrl {
|
impl std::fmt::Display for GrpcBaseUrl {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.url, f)
|
std::fmt::Display::fmt(&self.url, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for GrpcBaseUrl {
|
impl std::fmt::Debug for GrpcBaseUrl {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.url, f)
|
std::fmt::Debug::fmt(&self.url, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
crates/guardian_core/src/components/heading.rs
Normal file → Executable file
8
crates/guardian_core/src/components/heading.rs
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Heading(pub f64);
|
pub struct Heading(pub f64);
|
||||||
|
|||||||
44
crates/guardian_core/src/components/id.rs
Normal file → Executable file
44
crates/guardian_core/src/components/id.rs
Normal file → Executable file
@@ -1,22 +1,22 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Debug, Component, Copy, Clone, PartialEq)]
|
#[derive(Debug, Component, Copy, Clone, PartialEq)]
|
||||||
pub struct Id(u32);
|
pub struct Id(u32);
|
||||||
|
|
||||||
impl Id {
|
impl Id {
|
||||||
pub fn new(value: u32) -> Self {
|
pub fn new(value: u32) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Id> for u32 {
|
impl From<&Id> for u32 {
|
||||||
fn from(value: &Id) -> Self {
|
fn from(value: &Id) -> Self {
|
||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Id> for u32 {
|
impl From<Id> for u32 {
|
||||||
fn from(value: Id) -> Self {
|
fn from(value: Id) -> Self {
|
||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
crates/guardian_core/src/components/mod.rs
Normal file → Executable file
54
crates/guardian_core/src/components/mod.rs
Normal file → Executable file
@@ -1,27 +1,27 @@
|
|||||||
mod awacs;
|
mod awacs;
|
||||||
mod callsign;
|
mod callsign;
|
||||||
mod group;
|
mod group;
|
||||||
mod grpc_base_url;
|
mod grpc_base_url;
|
||||||
mod heading;
|
mod heading;
|
||||||
mod id;
|
mod id;
|
||||||
mod player;
|
mod player;
|
||||||
mod position;
|
mod position;
|
||||||
mod side;
|
mod side;
|
||||||
mod srs_socket_addr;
|
mod srs_socket_addr;
|
||||||
mod stt_base_url;
|
mod stt_base_url;
|
||||||
mod unit_type;
|
mod unit_type;
|
||||||
mod velocity;
|
mod velocity;
|
||||||
|
|
||||||
pub use awacs::*;
|
pub use awacs::*;
|
||||||
pub use callsign::*;
|
pub use callsign::*;
|
||||||
pub use group::*;
|
pub use group::*;
|
||||||
pub use grpc_base_url::*;
|
pub use grpc_base_url::*;
|
||||||
pub use heading::*;
|
pub use heading::*;
|
||||||
pub use id::*;
|
pub use id::*;
|
||||||
pub use player::*;
|
pub use player::*;
|
||||||
pub use position::*;
|
pub use position::*;
|
||||||
pub use side::*;
|
pub use side::*;
|
||||||
pub use srs_socket_addr::*;
|
pub use srs_socket_addr::*;
|
||||||
pub use stt_base_url::*;
|
pub use stt_base_url::*;
|
||||||
pub use unit_type::*;
|
pub use unit_type::*;
|
||||||
pub use velocity::*;
|
pub use velocity::*;
|
||||||
|
|||||||
158
crates/guardian_core/src/components/player.rs
Normal file → Executable file
158
crates/guardian_core/src/components/player.rs
Normal file → Executable file
@@ -1,79 +1,79 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{component::Component, reflect::ReflectComponent},
|
ecs::{component::Component, reflect::ReflectComponent},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Reflect, Component, Clone)]
|
#[derive(Reflect, Component, Clone)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
value: Cow<'static, str>,
|
value: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Player {
|
impl Default for Player {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Player::new("")
|
Player::new("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
/// Creates a new [`Player`] from any string-like type.
|
/// Creates a new [`Player`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
let mut value = Player { value, hash: 0 };
|
let mut value = Player { value, hash: 0 };
|
||||||
value.update_hash();
|
value.update_hash();
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's value.
|
/// Sets the entity's value.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
||||||
*self = Player::new(value);
|
*self = Player::new(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of the entity in place.
|
/// Updates the value of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the value was previously
|
/// This will allocate a new string if the value was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.value.to_mut());
|
f(self.value.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value of the entity as a `&str`.
|
/// Gets the value of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.value.hash(&mut hasher);
|
self.value.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Player {
|
impl std::fmt::Display for Player {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.value, f)
|
std::fmt::Display::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Player {
|
impl std::fmt::Debug for Player {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.value, f)
|
std::fmt::Debug::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
crates/guardian_core/src/components/position.rs
Normal file → Executable file
98
crates/guardian_core/src/components/position.rs
Normal file → Executable file
@@ -1,49 +1,49 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Debug, Component, Clone)]
|
#[derive(Debug, Component, Clone)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
pub long: f64,
|
pub long: f64,
|
||||||
pub altitude: f64,
|
pub altitude: f64,
|
||||||
}
|
}
|
||||||
const COMPASS_DRIFT: i32 = -5;
|
const COMPASS_DRIFT: i32 = -5;
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
pub fn distance_to_km(&self, other: &Position) -> f64 {
|
pub fn distance_to_km(&self, other: &Position) -> f64 {
|
||||||
let radius_of_earth_in_km: f64 = 6371.0;
|
let radius_of_earth_in_km: f64 = 6371.0;
|
||||||
let delta_lat = (other.lat - self.lat).to_radians();
|
let delta_lat = (other.lat - self.lat).to_radians();
|
||||||
let delta_long = (other.long - self.long).to_radians();
|
let delta_long = (other.long - self.long).to_radians();
|
||||||
|
|
||||||
let a = (delta_lat / 2.0).sin() * (delta_lat / 2.0).sin()
|
let a = (delta_lat / 2.0).sin() * (delta_lat / 2.0).sin()
|
||||||
+ self.lat.to_radians().cos()
|
+ self.lat.to_radians().cos()
|
||||||
* other.lat.to_radians().cos()
|
* other.lat.to_radians().cos()
|
||||||
* (delta_long / 2.0).sin()
|
* (delta_long / 2.0).sin()
|
||||||
* (delta_long / 2.0).sin();
|
* (delta_long / 2.0).sin();
|
||||||
|
|
||||||
let c = 2.0 * (a.sqrt().atan2((1.0 - a).sqrt()));
|
let c = 2.0 * (a.sqrt().atan2((1.0 - a).sqrt()));
|
||||||
radius_of_earth_in_km * c
|
radius_of_earth_in_km * c
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn distance_to_nmi(&self, other: &Position) -> f64 {
|
pub fn distance_to_nmi(&self, other: &Position) -> f64 {
|
||||||
self.distance_to_km(other) * 0.5399568035
|
self.distance_to_km(other) * 0.5399568035
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bearing_to(&self, other: &Position) -> i32 {
|
pub fn get_bearing_to(&self, other: &Position) -> i32 {
|
||||||
let delta_long = (other.long - self.long).to_radians();
|
let delta_long = (other.long - self.long).to_radians();
|
||||||
|
|
||||||
let y =
|
let y =
|
||||||
(other.long.to_radians() - self.long.to_radians()).sin() * other.lat.to_radians().cos();
|
(other.long.to_radians() - self.long.to_radians()).sin() * other.lat.to_radians().cos();
|
||||||
let x = self.lat.to_radians().cos() * other.lat.to_radians().sin()
|
let x = self.lat.to_radians().cos() * other.lat.to_radians().sin()
|
||||||
- self.lat.to_radians().sin() * other.lat.to_radians().cos() * delta_long.cos();
|
- self.lat.to_radians().sin() * other.lat.to_radians().cos() * delta_long.cos();
|
||||||
|
|
||||||
(((y.atan2(x).to_degrees() + 360.0) % 360.0) as i32) + COMPASS_DRIFT
|
(((y.atan2(x).to_degrees() + 360.0) % 360.0) as i32) + COMPASS_DRIFT
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn feet(&self) -> f64 {
|
pub fn feet(&self) -> f64 {
|
||||||
self.altitude * 3.28084
|
self.altitude * 3.28084
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn angels(&self) -> i32 {
|
pub fn angels(&self) -> i32 {
|
||||||
(self.feet() / 1000.0).round() as i32
|
(self.feet() / 1000.0).round() as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
crates/guardian_core/src/components/side.rs
Normal file → Executable file
20
crates/guardian_core/src/components/side.rs
Normal file → Executable file
@@ -1,10 +1,10 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct All;
|
pub struct All;
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Neutral;
|
pub struct Neutral;
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Red;
|
pub struct Red;
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Blue;
|
pub struct Blue;
|
||||||
|
|||||||
68
crates/guardian_core/src/components/srs_socket_addr.rs
Normal file → Executable file
68
crates/guardian_core/src/components/srs_socket_addr.rs
Normal file → Executable file
@@ -1,34 +1,34 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use bevy::ecs::system::Resource;
|
use bevy::ecs::system::Resource;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Resource, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Resource, Clone, Deserialize, Serialize)]
|
||||||
pub struct SrsSocketAddr(
|
pub struct SrsSocketAddr(
|
||||||
/// The server details for DCS-SRS
|
/// The server details for DCS-SRS
|
||||||
SocketAddr,
|
SocketAddr,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Default for SrsSocketAddr {
|
impl Default for SrsSocketAddr {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self("127.0.0.1:5002".parse().unwrap())
|
Self("127.0.0.1:5002".parse().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SrsSocketAddr {
|
impl SrsSocketAddr {
|
||||||
pub fn new(addr: SocketAddr) -> Self {
|
pub fn new(addr: SocketAddr) -> Self {
|
||||||
Self(addr)
|
Self(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SocketAddr> for SrsSocketAddr {
|
impl From<SocketAddr> for SrsSocketAddr {
|
||||||
fn from(value: SocketAddr) -> Self {
|
fn from(value: SocketAddr) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SrsSocketAddr> for SocketAddr {
|
impl From<SrsSocketAddr> for SocketAddr {
|
||||||
fn from(value: SrsSocketAddr) -> Self {
|
fn from(value: SrsSocketAddr) -> Self {
|
||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
164
crates/guardian_core/src/components/stt_base_url.rs
Normal file → Executable file
164
crates/guardian_core/src/components/stt_base_url.rs
Normal file → Executable file
@@ -1,82 +1,82 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{reflect::ReflectResource, system::Resource},
|
ecs::{reflect::ReflectResource, system::Resource},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Reflect, Resource, Clone, Deserialize, Serialize)]
|
#[derive(Reflect, Resource, Clone, Deserialize, Serialize)]
|
||||||
#[reflect(Resource, Default, Debug)]
|
#[reflect(Resource, Default, Debug)]
|
||||||
pub struct SttBaseUrl {
|
pub struct SttBaseUrl {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
hash: u64,
|
hash: u64,
|
||||||
/// The url to the [whisper-web](https://git.avii.nl/Guardian/whisper-web) instance
|
/// The url to the [whisper-web](https://git.avii.nl/Guardian/whisper-web) instance
|
||||||
url: Cow<'static, str>,
|
url: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SttBaseUrl {
|
impl Default for SttBaseUrl {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
SttBaseUrl::new("http://127.0.0.1:3000/")
|
SttBaseUrl::new("http://127.0.0.1:3000/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SttBaseUrl {
|
impl SttBaseUrl {
|
||||||
/// Creates a new [`SttBaseUrl`] from any string-like type.
|
/// Creates a new [`SttBaseUrl`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(url: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(url: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let url = url.into();
|
let url = url.into();
|
||||||
let mut url = SttBaseUrl { url, hash: 0 };
|
let mut url = SttBaseUrl { url, hash: 0 };
|
||||||
url.update_hash();
|
url.update_hash();
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's url.
|
/// Sets the entity's url.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, url: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, url: impl Into<Cow<'static, str>>) {
|
||||||
*self = SttBaseUrl::new(url);
|
*self = SttBaseUrl::new(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the url of the entity in place.
|
/// Updates the url of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the url was previously
|
/// This will allocate a new string if the url was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.url.to_mut());
|
f(self.url.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the url of the entity as a `&str`.
|
/// Gets the url of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.url
|
&self.url
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.url.hash(&mut hasher);
|
self.url.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SttBaseUrl {
|
impl std::fmt::Display for SttBaseUrl {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.url, f)
|
std::fmt::Display::fmt(&self.url, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for SttBaseUrl {
|
impl std::fmt::Debug for SttBaseUrl {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.url, f)
|
std::fmt::Debug::fmt(&self.url, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
158
crates/guardian_core/src/components/unit_type.rs
Normal file → Executable file
158
crates/guardian_core/src/components/unit_type.rs
Normal file → Executable file
@@ -1,79 +1,79 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{component::Component, reflect::ReflectComponent},
|
ecs::{component::Component, reflect::ReflectComponent},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Reflect, Component, Clone)]
|
#[derive(Reflect, Component, Clone)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct UnitType {
|
pub struct UnitType {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
value: Cow<'static, str>,
|
value: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UnitType {
|
impl Default for UnitType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
UnitType::new("")
|
UnitType::new("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnitType {
|
impl UnitType {
|
||||||
/// Creates a new [`UnitType`] from any string-like type.
|
/// Creates a new [`UnitType`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
let mut value = UnitType { value, hash: 0 };
|
let mut value = UnitType { value, hash: 0 };
|
||||||
value.update_hash();
|
value.update_hash();
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's value.
|
/// Sets the entity's value.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, value: impl Into<Cow<'static, str>>) {
|
||||||
*self = UnitType::new(value);
|
*self = UnitType::new(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the value of the entity in place.
|
/// Updates the value of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the value was previously
|
/// This will allocate a new string if the value was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.value.to_mut());
|
f(self.value.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value of the entity as a `&str`.
|
/// Gets the value of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.value.hash(&mut hasher);
|
self.value.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for UnitType {
|
impl std::fmt::Display for UnitType {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.value, f)
|
std::fmt::Display::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for UnitType {
|
impl std::fmt::Debug for UnitType {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.value, f)
|
std::fmt::Debug::fmt(&self.value, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
crates/guardian_core/src/components/velocity.rs
Normal file → Executable file
28
crates/guardian_core/src/components/velocity.rs
Normal file → Executable file
@@ -1,14 +1,14 @@
|
|||||||
use bevy::ecs::component::Component;
|
use bevy::ecs::component::Component;
|
||||||
use dcs_grpc::dcs::common::v0::Vector;
|
use dcs_grpc::dcs::common::v0::Vector;
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Velocity(pub Vector);
|
pub struct Velocity(pub Vector);
|
||||||
|
|
||||||
impl Velocity {
|
impl Velocity {
|
||||||
pub fn speed(&self) -> f64 {
|
pub fn speed(&self) -> f64 {
|
||||||
let x = self.0.x;
|
let x = self.0.x;
|
||||||
let y = self.0.y;
|
let y = self.0.y;
|
||||||
let z = self.0.z;
|
let z = self.0.z;
|
||||||
(x * x + y * y + z * z).sqrt()
|
(x * x + y * y + z * z).sqrt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
crates/guardian_core/src/dcs.rs
Normal file → Executable file
58
crates/guardian_core/src/dcs.rs
Normal file → Executable file
@@ -1,29 +1,29 @@
|
|||||||
pub mod mission;
|
pub mod mission;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
use crate::components::GrpcBaseUrl;
|
use crate::components::GrpcBaseUrl;
|
||||||
|
|
||||||
use mission::MissionPlugin;
|
use mission::MissionPlugin;
|
||||||
use text::TextPlugin;
|
use text::TextPlugin;
|
||||||
|
|
||||||
// Re-export
|
// Re-export
|
||||||
pub use dcs_grpc::dcs::common::v0::Coalition;
|
pub use dcs_grpc::dcs::common::v0::Coalition;
|
||||||
|
|
||||||
use bevy::app::{App, Plugin, ScheduleRunnerPlugin};
|
use bevy::app::{App, Plugin, ScheduleRunnerPlugin};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct DcsPlugin;
|
pub struct DcsPlugin;
|
||||||
|
|
||||||
impl Plugin for DcsPlugin {
|
impl Plugin for DcsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.world.get_resource_or_insert_with(GrpcBaseUrl::default);
|
app.world.get_resource_or_insert_with(GrpcBaseUrl::default);
|
||||||
|
|
||||||
// Make the app loop forever at 60fps.
|
// Make the app loop forever at 60fps.
|
||||||
app.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
|
app.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
|
||||||
1.0 / 60.0,
|
1.0 / 60.0,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
app.add_plugins(MissionPlugin);
|
app.add_plugins(MissionPlugin);
|
||||||
app.add_plugins(TextPlugin);
|
app.add_plugins(TextPlugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
514
crates/guardian_core/src/dcs/mission.rs
Normal file → Executable file
514
crates/guardian_core/src/dcs/mission.rs
Normal file → Executable file
@@ -1,257 +1,257 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{components::*, TokioResource};
|
use crate::{components::*, TokioResource};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::{App, Plugin, PostUpdate, PreStartup, PreUpdate},
|
app::{App, Plugin, PostUpdate, PreStartup, PreUpdate},
|
||||||
core::Name,
|
core::Name,
|
||||||
ecs::{
|
ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventReader, EventWriter},
|
event::{Event, EventReader, EventWriter},
|
||||||
query::With,
|
query::With,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crossbeam_channel::{unbounded, Receiver};
|
use crossbeam_channel::{unbounded, Receiver};
|
||||||
use dcs_grpc::dcs::{
|
use dcs_grpc::dcs::{
|
||||||
common::v0::{Coalition, GroupCategory, Unit},
|
common::v0::{Coalition, GroupCategory, Unit},
|
||||||
mission::v0::{
|
mission::v0::{
|
||||||
mission_service_client::MissionServiceClient, stream_units_response::Update,
|
mission_service_client::MissionServiceClient, stream_units_response::Update,
|
||||||
StreamUnitsRequest,
|
StreamUnitsRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Response {
|
enum Response {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Update(Box<Update>),
|
Update(Box<Update>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MissionPlugin;
|
pub struct MissionPlugin;
|
||||||
|
|
||||||
impl Plugin for MissionPlugin {
|
impl Plugin for MissionPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<UnitUpdatedEvent>();
|
app.add_event::<UnitUpdatedEvent>();
|
||||||
app.add_event::<UnitGoneEvent>();
|
app.add_event::<UnitGoneEvent>();
|
||||||
|
|
||||||
app.add_systems(PreStartup, connect_to_grpc);
|
app.add_systems(PreStartup, connect_to_grpc);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
(consume_stream_message, update_units, despawn_units),
|
(consume_stream_message, update_units, despawn_units),
|
||||||
)
|
)
|
||||||
.add_systems(PostUpdate, cleanup_after_disconnect);
|
.add_systems(PostUpdate, cleanup_after_disconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_to_grpc(mut commands: Commands, tokio: Res<TokioResource>, url: Res<GrpcBaseUrl>) {
|
fn connect_to_grpc(mut commands: Commands, tokio: Res<TokioResource>, url: Res<GrpcBaseUrl>) {
|
||||||
let handle = &tokio.0;
|
let handle = &tokio.0;
|
||||||
|
|
||||||
let (tx, task) = unbounded();
|
let (tx, task) = unbounded();
|
||||||
|
|
||||||
let poll_rate = url.poll_rate();
|
let poll_rate = url.poll_rate();
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
|
|
||||||
handle.spawn(async move {
|
handle.spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let Ok(mut client) = MissionServiceClient::connect(url.clone()).await else {
|
let Ok(mut client) = MissionServiceClient::connect(url.clone()).await else {
|
||||||
warn!("Connection failed: {}", url); // warn
|
warn!("Connection failed: {}", url); // warn
|
||||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
info!("Connected to DCS: {}", url);
|
info!("Connected to DCS: {}", url);
|
||||||
|
|
||||||
let Ok(mut stream) = client
|
let Ok(mut stream) = client
|
||||||
.stream_units(StreamUnitsRequest {
|
.stream_units(StreamUnitsRequest {
|
||||||
poll_rate,
|
poll_rate,
|
||||||
max_backoff: Some(30),
|
max_backoff: Some(30),
|
||||||
category: GroupCategory::Airplane as i32,
|
category: GroupCategory::Airplane as i32,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match stream.get_mut().message().await {
|
match stream.get_mut().message().await {
|
||||||
Ok(Some(next)) => {
|
Ok(Some(next)) => {
|
||||||
if let Some(update) = &next.update {
|
if let Some(update) = &next.update {
|
||||||
tx.send(Response::Update(Box::new(update.clone()))).ok();
|
tx.send(Response::Update(Box::new(update.clone()))).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error from gRPC: {:?}", e); // verbose or debug log
|
error!("Error from gRPC: {:?}", e); // verbose or debug log
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
warn!("Disconnected from DCS: {}", url); // warn
|
warn!("Disconnected from DCS: {}", url); // warn
|
||||||
tx.send(Response::Disconnected).ok();
|
tx.send(Response::Disconnected).ok();
|
||||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.spawn(UnitsRequestMessage(task));
|
commands.spawn(UnitsRequestMessage(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup_after_disconnect(
|
fn cleanup_after_disconnect(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
disconnected: Query<Entity, With<Disconnected>>,
|
disconnected: Query<Entity, With<Disconnected>>,
|
||||||
entitiews: Query<Entity, With<Id>>,
|
entitiews: Query<Entity, With<Id>>,
|
||||||
) {
|
) {
|
||||||
for d in disconnected.iter() {
|
for d in disconnected.iter() {
|
||||||
// if there is one
|
// if there is one
|
||||||
commands.entity(d).despawn();
|
commands.entity(d).despawn();
|
||||||
|
|
||||||
for ent in entitiews.iter() {
|
for ent in entitiews.iter() {
|
||||||
commands.entity(ent).despawn();
|
commands.entity(ent).despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_stream_message(
|
fn consume_stream_message(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
requests: Query<&UnitsRequestMessage>,
|
requests: Query<&UnitsRequestMessage>,
|
||||||
mut ev_unit_updated: EventWriter<UnitUpdatedEvent>,
|
mut ev_unit_updated: EventWriter<UnitUpdatedEvent>,
|
||||||
mut ev_unit_gone: EventWriter<UnitGoneEvent>,
|
mut ev_unit_gone: EventWriter<UnitGoneEvent>,
|
||||||
) {
|
) {
|
||||||
for stream in requests.iter() {
|
for stream in requests.iter() {
|
||||||
if let Ok(update) = stream.0.try_recv() {
|
if let Ok(update) = stream.0.try_recv() {
|
||||||
match update {
|
match update {
|
||||||
Response::Update(boxed) => match *boxed {
|
Response::Update(boxed) => match *boxed {
|
||||||
Update::Gone(unit) => {
|
Update::Gone(unit) => {
|
||||||
ev_unit_gone.send(UnitGoneEvent(unit.id));
|
ev_unit_gone.send(UnitGoneEvent(unit.id));
|
||||||
}
|
}
|
||||||
Update::Unit(unit) => {
|
Update::Unit(unit) => {
|
||||||
ev_unit_updated.send(UnitUpdatedEvent(unit));
|
ev_unit_updated.send(UnitUpdatedEvent(unit));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Response::Disconnected => {
|
Response::Disconnected => {
|
||||||
commands.spawn(Disconnected);
|
commands.spawn(Disconnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_units(
|
fn update_units(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
units: Query<(Entity, &Id)>,
|
units: Query<(Entity, &Id)>,
|
||||||
mut ev: EventReader<UnitUpdatedEvent>,
|
mut ev: EventReader<UnitUpdatedEvent>,
|
||||||
) {
|
) {
|
||||||
for event in ev.read() {
|
for event in ev.read() {
|
||||||
let event = &event.0;
|
let event = &event.0;
|
||||||
|
|
||||||
let mut e: Option<_> = None;
|
let mut e: Option<_> = None;
|
||||||
for (ent, id) in units.iter() {
|
for (ent, id) in units.iter() {
|
||||||
if &Id::new(event.id) == id {
|
if &Id::new(event.id) == id {
|
||||||
e = commands.get_entity(ent);
|
e = commands.get_entity(ent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.is_none() {
|
if e.is_none() {
|
||||||
e = Some(commands.spawn((Id::new(event.id), Callsign::new(event.callsign.clone()))));
|
e = Some(commands.spawn((Id::new(event.id), Callsign::new(event.callsign.clone()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(mut e) = e else {
|
let Some(mut e) = e else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(position) = &event.position {
|
if let Some(position) = &event.position {
|
||||||
e.insert(Position {
|
e.insert(Position {
|
||||||
lat: position.lat,
|
lat: position.lat,
|
||||||
long: position.lon,
|
long: position.lon,
|
||||||
altitude: position.alt,
|
altitude: position.alt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(orientation) = &event.orientation {
|
if let Some(orientation) = &event.orientation {
|
||||||
e.insert(Heading(orientation.heading));
|
e.insert(Heading(orientation.heading));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(velocity) = &event.velocity {
|
if let Some(velocity) = &event.velocity {
|
||||||
if let Some(velocity) = &velocity.velocity {
|
if let Some(velocity) = &velocity.velocity {
|
||||||
e.insert(Velocity(velocity.clone()));
|
e.insert(Velocity(velocity.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(playername) = &event.player_name {
|
if let Some(playername) = &event.player_name {
|
||||||
let playername = playername.clone();
|
let playername = playername.clone();
|
||||||
e.insert(Player::new(playername.clone()));
|
e.insert(Player::new(playername.clone()));
|
||||||
|
|
||||||
if let Some(index) = playername.find('|') {
|
if let Some(index) = playername.find('|') {
|
||||||
let custom_callsign = playername[..index].trim().to_string();
|
let custom_callsign = playername[..index].trim().to_string();
|
||||||
|
|
||||||
// testing callsign validity
|
// testing callsign validity
|
||||||
if guardian_commands::call::parse_call(&format!(
|
if guardian_commands::call::parse_call(&format!(
|
||||||
"Overlord {} radio check",
|
"Overlord {} radio check",
|
||||||
custom_callsign
|
custom_callsign
|
||||||
))
|
))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
e.insert(Callsign::new(custom_callsign.clone()));
|
e.insert(Callsign::new(custom_callsign.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(group) = &event.group {
|
if let Some(group) = &event.group {
|
||||||
e.insert(Group {
|
e.insert(Group {
|
||||||
id: group.id,
|
id: group.id,
|
||||||
unit: event.number_in_group,
|
unit: event.number_in_group,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
e.insert(Name::new(event.name.clone()));
|
e.insert(Name::new(event.name.clone()));
|
||||||
|
|
||||||
if ["A-50", "E-3A", "E-2C", "KJ-2000"].contains(&event.r#type.as_str()) {
|
if ["A-50", "E-3A", "E-2C", "KJ-2000"].contains(&event.r#type.as_str()) {
|
||||||
e.insert(Awacs);
|
e.insert(Awacs);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.insert(UnitType::new(event.r#type.clone()));
|
e.insert(UnitType::new(event.r#type.clone()));
|
||||||
|
|
||||||
{
|
{
|
||||||
e.remove::<All>();
|
e.remove::<All>();
|
||||||
e.remove::<Neutral>();
|
e.remove::<Neutral>();
|
||||||
e.remove::<Red>();
|
e.remove::<Red>();
|
||||||
e.remove::<Blue>();
|
e.remove::<Blue>();
|
||||||
match Coalition::try_from(event.coalition).expect("Coalition to be correct") {
|
match Coalition::try_from(event.coalition).expect("Coalition to be correct") {
|
||||||
Coalition::All => e.insert(All),
|
Coalition::All => e.insert(All),
|
||||||
Coalition::Neutral => e.insert(Neutral),
|
Coalition::Neutral => e.insert(Neutral),
|
||||||
Coalition::Red => e.insert(Red),
|
Coalition::Red => e.insert(Red),
|
||||||
Coalition::Blue => e.insert(Blue),
|
Coalition::Blue => e.insert(Blue),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn despawn_units(
|
fn despawn_units(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
units: Query<(Entity, &Id)>,
|
units: Query<(Entity, &Id)>,
|
||||||
mut ev: EventReader<UnitGoneEvent>,
|
mut ev: EventReader<UnitGoneEvent>,
|
||||||
) {
|
) {
|
||||||
for event in ev.read() {
|
for event in ev.read() {
|
||||||
let gid = event.0;
|
let gid = event.0;
|
||||||
for (ent, id) in units.iter() {
|
for (ent, id) in units.iter() {
|
||||||
if &Id::new(gid) == id {
|
if &Id::new(gid) == id {
|
||||||
commands.entity(ent).despawn();
|
commands.entity(ent).despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct UnitUpdatedEvent(Unit);
|
struct UnitUpdatedEvent(Unit);
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct UnitGoneEvent(u32);
|
struct UnitGoneEvent(u32);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct UnitsRequestMessage(Receiver<Response>);
|
struct UnitsRequestMessage(Receiver<Response>);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Disconnected;
|
struct Disconnected;
|
||||||
|
|||||||
282
crates/guardian_core/src/dcs/text.rs
Normal file → Executable file
282
crates/guardian_core/src/dcs/text.rs
Normal file → Executable file
@@ -1,141 +1,141 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::{Plugin, PostUpdate},
|
app::{Plugin, PostUpdate},
|
||||||
ecs::{
|
ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
query::Added,
|
query::Added,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
},
|
},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
use dcs_grpc::dcs::trigger::v0::{
|
use dcs_grpc::dcs::trigger::v0::{
|
||||||
trigger_service_client::TriggerServiceClient, OutTextForUnitRequest,
|
trigger_service_client::TriggerServiceClient, OutTextForUnitRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{GrpcBaseUrl, Id},
|
components::{GrpcBaseUrl, Id},
|
||||||
TokioResource,
|
TokioResource,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TextPlugin;
|
pub struct TextPlugin;
|
||||||
|
|
||||||
impl Plugin for TextPlugin {
|
impl Plugin for TextPlugin {
|
||||||
fn build(&self, app: &mut bevy::prelude::App) {
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
app.add_systems(PostUpdate, send_text_message);
|
app.add_systems(PostUpdate, send_text_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_text_message(
|
fn send_text_message(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
query: Query<(Entity, &Id, &TextMessage), Added<TextMessage>>,
|
query: Query<(Entity, &Id, &TextMessage), Added<TextMessage>>,
|
||||||
url: Res<GrpcBaseUrl>,
|
url: Res<GrpcBaseUrl>,
|
||||||
tokio: Res<TokioResource>,
|
tokio: Res<TokioResource>,
|
||||||
) {
|
) {
|
||||||
for (ent, id, msg) in query.iter() {
|
for (ent, id, msg) in query.iter() {
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
|
|
||||||
if msg.as_str().is_empty() {
|
if msg.as_str().is_empty() {
|
||||||
commands.entity(ent).remove::<TextMessage>();
|
commands.entity(ent).remove::<TextMessage>();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = msg.clone();
|
let message = msg.clone();
|
||||||
let url = url.clone();
|
let url = url.clone();
|
||||||
let id: u32 = id.into();
|
let id: u32 = id.into();
|
||||||
tokio.0.spawn(async move {
|
tokio.0.spawn(async move {
|
||||||
let Ok(mut client) = TriggerServiceClient::connect(url.to_string()).await else {
|
let Ok(mut client) = TriggerServiceClient::connect(url.to_string()).await else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = OutTextForUnitRequest {
|
let request = OutTextForUnitRequest {
|
||||||
text: message.to_string(),
|
text: message.to_string(),
|
||||||
display_time: 15,
|
display_time: 15,
|
||||||
clear_view: false,
|
clear_view: false,
|
||||||
unit_id: id,
|
unit_id: id,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("> {}", message);
|
info!("> {}", message);
|
||||||
client.out_text_for_unit(request).await.ok();
|
client.out_text_for_unit(request).await.ok();
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.entity(ent).remove::<TextMessage>();
|
commands.entity(ent).remove::<TextMessage>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Reflect, Component, Clone)]
|
#[derive(Reflect, Component, Clone)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct TextMessage {
|
pub struct TextMessage {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
message: Cow<'static, str>,
|
message: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextMessage {
|
impl Default for TextMessage {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TextMessage::new("")
|
TextMessage::new("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextMessage {
|
impl TextMessage {
|
||||||
/// Creates a new [`TextMessage`] from any string-like type.
|
/// Creates a new [`TextMessage`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
let mut message = TextMessage { message, hash: 0 };
|
let mut message = TextMessage { message, hash: 0 };
|
||||||
message.update_hash();
|
message.update_hash();
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's message.
|
/// Sets the entity's message.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, message: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, message: impl Into<Cow<'static, str>>) {
|
||||||
*self = TextMessage::new(message);
|
*self = TextMessage::new(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the message of the entity in place.
|
/// Updates the message of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the message was previously
|
/// This will allocate a new string if the message was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.message.to_mut());
|
f(self.message.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the message of the entity as a `&str`.
|
/// Gets the message of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.message
|
&self.message
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.message.hash(&mut hasher);
|
self.message.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for TextMessage {
|
impl std::fmt::Display for TextMessage {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.message, f)
|
std::fmt::Display::fmt(&self.message, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for TextMessage {
|
impl std::fmt::Debug for TextMessage {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.message, f)
|
std::fmt::Debug::fmt(&self.message, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
crates/guardian_core/src/lib.rs
Normal file → Executable file
58
crates/guardian_core/src/lib.rs
Normal file → Executable file
@@ -1,29 +1,29 @@
|
|||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod dcs;
|
pub mod dcs;
|
||||||
pub mod srs;
|
pub mod srs;
|
||||||
mod tts;
|
mod tts;
|
||||||
|
|
||||||
use dcs::DcsPlugin;
|
use dcs::DcsPlugin;
|
||||||
use srs::SrsPlugin;
|
use srs::SrsPlugin;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::{PluginGroup, PluginGroupBuilder},
|
app::{PluginGroup, PluginGroupBuilder},
|
||||||
ecs::system::Resource,
|
ecs::system::Resource,
|
||||||
};
|
};
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
|
|
||||||
pub struct DefaultPlugins;
|
pub struct DefaultPlugins;
|
||||||
|
|
||||||
impl PluginGroup for DefaultPlugins {
|
impl PluginGroup for DefaultPlugins {
|
||||||
fn build(self) -> PluginGroupBuilder {
|
fn build(self) -> PluginGroupBuilder {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut group = PluginGroupBuilder::start::<Self>()
|
let mut group = PluginGroupBuilder::start::<Self>()
|
||||||
.add(DcsPlugin)
|
.add(DcsPlugin)
|
||||||
.add(SrsPlugin);
|
.add(SrsPlugin);
|
||||||
|
|
||||||
group
|
group
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct TokioResource(pub Handle);
|
pub struct TokioResource(pub Handle);
|
||||||
|
|||||||
3
crates/guardian_core/src/srs.rs
Normal file → Executable file
3
crates/guardian_core/src/srs.rs
Normal file → Executable file
@@ -261,7 +261,8 @@ fn listen_srs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(data) = voice_handle.recv() => {
|
Some(data) = voice_handle.recv() => {
|
||||||
frames.push(synthesize("192.168.0.238:10200", data.as_str()).await?).await;
|
// TODO: Make this configurable
|
||||||
|
frames.push(synthesize("192.168.1.3:10200", data.as_str()).await?).await;
|
||||||
}
|
}
|
||||||
Some(data) = voice_stream.next() => {
|
Some(data) = voice_stream.next() => {
|
||||||
match data {
|
match data {
|
||||||
|
|||||||
64
crates/guardian_core/src/srs/frame_queue.rs
Normal file → Executable file
64
crates/guardian_core/src/srs/frame_queue.rs
Normal file → Executable file
@@ -1,32 +1,32 @@
|
|||||||
// a queue we can push to at will
|
// a queue we can push to at will
|
||||||
// we need to take an item and read async,stream?
|
// we need to take an item and read async,stream?
|
||||||
// pull from the front, push to the back
|
// pull from the front, push to the back
|
||||||
|
|
||||||
use std::{collections::VecDeque, sync::Arc};
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
pub struct FrameQueue<T> {
|
pub struct FrameQueue<T> {
|
||||||
backend: Arc<RwLock<VecDeque<T>>>,
|
backend: Arc<RwLock<VecDeque<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FrameQueue<T> {
|
impl<T> FrameQueue<T> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
backend: Arc::new(RwLock::new(VecDeque::new())),
|
backend: Arc::new(RwLock::new(VecDeque::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn push(&self, item: T) {
|
pub async fn push(&self, item: T) {
|
||||||
self.backend.write().await.push_back(item);
|
self.backend.write().await.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&self) -> Option<T> {
|
pub async fn next(&self) -> Option<T> {
|
||||||
self.backend.write().await.pop_front()
|
self.backend.write().await.pop_front()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for FrameQueue<T> {
|
impl<T> Default for FrameQueue<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
crates/guardian_core/src/srs/message.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/message.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/messages_codec.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/messages_codec.rs
Normal file → Executable file
160
crates/guardian_core/src/srs/voice.rs
Normal file → Executable file
160
crates/guardian_core/src/srs/voice.rs
Normal file → Executable file
@@ -1,80 +1,80 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::{component::Component, reflect::ReflectComponent},
|
ecs::{component::Component, reflect::ReflectComponent},
|
||||||
reflect::{std_traits::ReflectDefault, Reflect},
|
reflect::{std_traits::ReflectDefault, Reflect},
|
||||||
utils::AHasher,
|
utils::AHasher,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Reflect, Component, Clone)]
|
#[derive(Reflect, Component, Clone)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub struct VoiceMessage {
|
pub struct VoiceMessage {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
message: Cow<'static, str>,
|
message: Cow<'static, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VoiceMessage {
|
impl Default for VoiceMessage {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
VoiceMessage::new("")
|
VoiceMessage::new("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl VoiceMessage {
|
impl VoiceMessage {
|
||||||
/// Creates a new [`VoiceMessage`] from any string-like type.
|
/// Creates a new [`VoiceMessage`] from any string-like type.
|
||||||
///
|
///
|
||||||
/// The internal hash will be computed immediately.
|
/// The internal hash will be computed immediately.
|
||||||
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
|
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
let mut message = VoiceMessage { message, hash: 0 };
|
let mut message = VoiceMessage { message, hash: 0 };
|
||||||
message.update_hash();
|
message.update_hash();
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the entity's message.
|
/// Sets the entity's message.
|
||||||
///
|
///
|
||||||
/// The internal hash will be re-computed.
|
/// The internal hash will be re-computed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set(&mut self, message: impl Into<Cow<'static, str>>) {
|
pub fn set(&mut self, message: impl Into<Cow<'static, str>>) {
|
||||||
*self = VoiceMessage::new(message);
|
*self = VoiceMessage::new(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the message of the entity in place.
|
/// Updates the message of the entity in place.
|
||||||
///
|
///
|
||||||
/// This will allocate a new string if the message was previously
|
/// This will allocate a new string if the message was previously
|
||||||
/// created from a borrow.
|
/// created from a borrow.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
|
||||||
f(self.message.to_mut());
|
f(self.message.to_mut());
|
||||||
self.update_hash();
|
self.update_hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the message of the entity as a `&str`.
|
/// Gets the message of the entity as a `&str`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.message
|
&self.message
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hash(&mut self) {
|
fn update_hash(&mut self) {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.message.hash(&mut hasher);
|
self.message.hash(&mut hasher);
|
||||||
self.hash = hasher.finish();
|
self.hash = hasher.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for VoiceMessage {
|
impl std::fmt::Display for VoiceMessage {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(&self.message, f)
|
std::fmt::Display::fmt(&self.message, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for VoiceMessage {
|
impl std::fmt::Debug for VoiceMessage {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(&self.message, f)
|
std::fmt::Debug::fmt(&self.message, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
crates/guardian_core/src/srs/voice_codec.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/voice_codec.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/voice_command.rs
Normal file → Executable file
0
crates/guardian_core/src/srs/voice_command.rs
Normal file → Executable file
418
crates/guardian_core/src/tts.rs
Normal file → Executable file
418
crates/guardian_core/src/tts.rs
Normal file → Executable file
@@ -1,209 +1,209 @@
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use bevy::log;
|
use bevy::log;
|
||||||
use dasp::interpolate::sinc::Sinc;
|
use dasp::interpolate::sinc::Sinc;
|
||||||
use dasp::ring_buffer;
|
use dasp::ring_buffer;
|
||||||
use dasp::signal;
|
use dasp::signal;
|
||||||
use dasp::Sample;
|
use dasp::Sample;
|
||||||
use dasp::Signal;
|
use dasp::Signal;
|
||||||
|
|
||||||
use hound::WavSpec;
|
use hound::WavSpec;
|
||||||
use hound::WavWriter;
|
use hound::WavWriter;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::net::ToSocketAddrs;
|
use tokio::net::ToSocketAddrs;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Packet {
|
pub struct Packet {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub data_length: Option<usize>,
|
pub data_length: Option<usize>,
|
||||||
pub payload_length: Option<usize>,
|
pub payload_length: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct RequestData {
|
struct RequestData {
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Request {
|
struct Request {
|
||||||
r#type: String,
|
r#type: String,
|
||||||
data: RequestData,
|
data: RequestData,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn synthesize<A: ToSocketAddrs>(
|
pub async fn synthesize<A: ToSocketAddrs>(
|
||||||
addr: A,
|
addr: A,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
let stream = TcpStream::connect(addr).await?;
|
let stream = TcpStream::connect(addr).await?;
|
||||||
|
|
||||||
let (mut read, mut write) = tokio::io::split(stream);
|
let (mut read, mut write) = tokio::io::split(stream);
|
||||||
|
|
||||||
let request = Request {
|
let request = Request {
|
||||||
r#type: "synthesize".to_string(),
|
r#type: "synthesize".to_string(),
|
||||||
data: RequestData {
|
data: RequestData {
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut request = serde_json::to_string(&request)?;
|
let mut request = serde_json::to_string(&request)?;
|
||||||
request.push('\n');
|
request.push('\n');
|
||||||
|
|
||||||
write.write_all(request.as_bytes()).await?;
|
write.write_all(request.as_bytes()).await?;
|
||||||
|
|
||||||
let mut str = String::new();
|
let mut str = String::new();
|
||||||
let mut pcm_buffer: Vec<u8> = Vec::new();
|
let mut pcm_buffer: Vec<u8> = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
let Ok(c) = read.read_u8().await else {
|
let Ok(c) = read.read_u8().await else {
|
||||||
log::error!("Unable to read byte");
|
log::error!("Unable to read byte");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
if c == b'\n' {
|
if c == b'\n' {
|
||||||
let Ok(packet) = serde_json::from_str::<Packet>(&str) else {
|
let Ok(packet) = serde_json::from_str::<Packet>(&str) else {
|
||||||
log::error!("Unable to read packet from: {}", str);
|
log::error!("Unable to read packet from: {}", str);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
str = String::new();
|
str = String::new();
|
||||||
|
|
||||||
if let Some(length) = packet.data_length {
|
if let Some(length) = packet.data_length {
|
||||||
let mut buf = (0..length).map(|_| 0u8).collect::<Vec<_>>();
|
let mut buf = (0..length).map(|_| 0u8).collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Err(e) = read.read_exact(&mut buf).await {
|
if let Err(e) = read.read_exact(&mut buf).await {
|
||||||
log::error!("Unable to read data: {:?}", e);
|
log::error!("Unable to read data: {:?}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(length) = packet.payload_length {
|
if let Some(length) = packet.payload_length {
|
||||||
let mut buf = (0..length).map(|_| 0u8).collect::<Vec<_>>();
|
let mut buf = (0..length).map(|_| 0u8).collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Err(e) = read.read_exact(&mut buf).await {
|
if let Err(e) = read.read_exact(&mut buf).await {
|
||||||
log::error!("Unable to read data: {:?}", e);
|
log::error!("Unable to read data: {:?}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcm_buffer.append(&mut buf);
|
pcm_buffer.append(&mut buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
match packet.r#type.as_str() {
|
match packet.r#type.as_str() {
|
||||||
"audio-start" => {
|
"audio-start" => {
|
||||||
pcm_buffer.clear();
|
pcm_buffer.clear();
|
||||||
}
|
}
|
||||||
"audio-stop" => {
|
"audio-stop" => {
|
||||||
log::info!("Audio received, start post-processing");
|
log::info!("Audio received, start post-processing");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
str.push(c.into());
|
str.push(c.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let wav = to_wav(&pcm_buffer)?;
|
let wav = to_wav(&pcm_buffer)?;
|
||||||
|
|
||||||
let Ok(reader) = hound::WavReader::new(&*wav) else {
|
let Ok(reader) = hound::WavReader::new(&*wav) else {
|
||||||
log::error!("Error opening reader");
|
log::error!("Error opening reader");
|
||||||
return Err("Error opening reader".into());
|
return Err("Error opening reader".into());
|
||||||
};
|
};
|
||||||
|
|
||||||
// pcm_buffer is 22050Hz, need to convert this to 16000Hz
|
// pcm_buffer is 22050Hz, need to convert this to 16000Hz
|
||||||
let samples = reader
|
let samples = reader
|
||||||
.into_samples()
|
.into_samples()
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(i16::to_sample::<f64>);
|
.map(i16::to_sample::<f64>);
|
||||||
|
|
||||||
let signal = signal::from_interleaved_samples_iter(samples);
|
let signal = signal::from_interleaved_samples_iter(samples);
|
||||||
|
|
||||||
let ring_buffer = ring_buffer::Fixed::from([[0.0]; 100]);
|
let ring_buffer = ring_buffer::Fixed::from([[0.0]; 100]);
|
||||||
let sinc = Sinc::new(ring_buffer);
|
let sinc = Sinc::new(ring_buffer);
|
||||||
|
|
||||||
let new_signal = signal.from_hz_to_hz(sinc, 22050.0, 16000.0);
|
let new_signal = signal.from_hz_to_hz(sinc, 22050.0, 16000.0);
|
||||||
|
|
||||||
let spec = WavSpec {
|
let spec = WavSpec {
|
||||||
channels: 1,
|
channels: 1,
|
||||||
sample_rate: 16000,
|
sample_rate: 16000,
|
||||||
bits_per_sample: 16,
|
bits_per_sample: 16,
|
||||||
sample_format: hound::SampleFormat::Int,
|
sample_format: hound::SampleFormat::Int,
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_wav: Vec<u8> = Vec::new();
|
let new_wav: Vec<u8> = Vec::new();
|
||||||
let mut new_wav_cursor = Cursor::new(new_wav);
|
let mut new_wav_cursor = Cursor::new(new_wav);
|
||||||
|
|
||||||
let mut writer = WavWriter::new(&mut new_wav_cursor, spec).unwrap();
|
let mut writer = WavWriter::new(&mut new_wav_cursor, spec).unwrap();
|
||||||
|
|
||||||
for frame in new_signal.until_exhausted() {
|
for frame in new_signal.until_exhausted() {
|
||||||
writer.write_sample(frame[0].to_sample::<i16>()).unwrap();
|
writer.write_sample(frame[0].to_sample::<i16>()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.flush().unwrap();
|
writer.flush().unwrap();
|
||||||
|
|
||||||
drop(writer);
|
drop(writer);
|
||||||
|
|
||||||
let wav = new_wav_cursor.into_inner();
|
let wav = new_wav_cursor.into_inner();
|
||||||
let wav = bytes::Bytes::copy_from_slice(&wav);
|
let wav = bytes::Bytes::copy_from_slice(&wav);
|
||||||
|
|
||||||
Ok(wav_to_opus(wav).await?)
|
Ok(wav_to_opus(wav).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wav_to_opus(wav: bytes::Bytes) -> Result<Vec<Vec<u8>>, audiopus::Error> {
|
async fn wav_to_opus(wav: bytes::Bytes) -> Result<Vec<Vec<u8>>, audiopus::Error> {
|
||||||
use audiopus::coder::Encoder;
|
use audiopus::coder::Encoder;
|
||||||
use audiopus::{Application, Channels, SampleRate};
|
use audiopus::{Application, Channels, SampleRate};
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let audio_stream = wav
|
let audio_stream = wav
|
||||||
.chunks(2)
|
.chunks(2)
|
||||||
.map(|bytes| i16::from_le_bytes(bytes.try_into().unwrap()))
|
.map(|bytes| i16::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
const MONO_20MS: usize = 16000 /* 1 channel */ * 20 / 1000;
|
const MONO_20MS: usize = 16000 /* 1 channel */ * 20 / 1000;
|
||||||
let enc = Encoder::new(SampleRate::Hz16000, Channels::Mono, Application::Voip)?;
|
let enc = Encoder::new(SampleRate::Hz16000, Channels::Mono, Application::Voip)?;
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
let mut output = [0; 256];
|
let mut output = [0; 256];
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
|
|
||||||
while pos + MONO_20MS < audio_stream.len() {
|
while pos + MONO_20MS < audio_stream.len() {
|
||||||
let len = enc.encode(&audio_stream[pos..(pos + MONO_20MS)], &mut output)?;
|
let len = enc.encode(&audio_stream[pos..(pos + MONO_20MS)], &mut output)?;
|
||||||
frames.push(output[..len].to_vec());
|
frames.push(output[..len].to_vec());
|
||||||
|
|
||||||
pos += MONO_20MS;
|
pos += MONO_20MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<_, audiopus::Error>(frames)
|
Ok::<_, audiopus::Error>(frames)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_wav(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
fn to_wav(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
let input_buffer: Vec<u8> = Vec::new();
|
let input_buffer: Vec<u8> = Vec::new();
|
||||||
let mut input_buffer_cursor = Cursor::new(input_buffer);
|
let mut input_buffer_cursor = Cursor::new(input_buffer);
|
||||||
|
|
||||||
let input_spec = WavSpec {
|
let input_spec = WavSpec {
|
||||||
channels: 1,
|
channels: 1,
|
||||||
sample_rate: 22050,
|
sample_rate: 22050,
|
||||||
bits_per_sample: 16,
|
bits_per_sample: 16,
|
||||||
sample_format: hound::SampleFormat::Int,
|
sample_format: hound::SampleFormat::Int,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut input = hound::WavWriter::new(&mut input_buffer_cursor, input_spec)?;
|
let mut input = hound::WavWriter::new(&mut input_buffer_cursor, input_spec)?;
|
||||||
|
|
||||||
let audio_stream = data
|
let audio_stream = data
|
||||||
.chunks(2)
|
.chunks(2)
|
||||||
.map(|bytes| i16::from_le_bytes(bytes.try_into().unwrap()))
|
.map(|bytes| i16::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for frame in audio_stream {
|
for frame in audio_stream {
|
||||||
input.write_sample(frame.to_sample::<i16>()).unwrap();
|
input.write_sample(frame.to_sample::<i16>()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(input);
|
drop(input);
|
||||||
|
|
||||||
Ok(input_buffer_cursor.into_inner())
|
Ok(input_buffer_cursor.into_inner())
|
||||||
}
|
}
|
||||||
|
|||||||
4
rust-toolchain.toml
Normal file → Executable file
4
rust-toolchain.toml
Normal file → Executable file
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "stable"
|
channel = "stable"
|
||||||
|
|||||||
194
src/braa.rs
Normal file → Executable file
194
src/braa.rs
Normal file → Executable file
@@ -1,97 +1,97 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use guardian_core::components::{Heading, Position};
|
use guardian_core::components::{Heading, Position};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Aspect {
|
pub enum Aspect {
|
||||||
Hot,
|
Hot,
|
||||||
Flank,
|
Flank,
|
||||||
Cold,
|
Cold,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Aspect {
|
impl std::fmt::Display for Aspect {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Hot => write!(f, "Hot"),
|
Self::Hot => write!(f, "Hot"),
|
||||||
Self::Flank => write!(f, "Flank"),
|
Self::Flank => write!(f, "Flank"),
|
||||||
Self::Cold => write!(f, "Cold"),
|
Self::Cold => write!(f, "Cold"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Braa {
|
pub struct Braa {
|
||||||
bearing: i32,
|
bearing: i32,
|
||||||
range: i32,
|
range: i32,
|
||||||
angels: i32,
|
angels: i32,
|
||||||
aspect: Aspect,
|
aspect: Aspect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Braa {
|
impl Display for Braa {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.to_text())
|
write!(f, "{}", self.to_text())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl Braa {
|
impl Braa {
|
||||||
pub fn new(unit_a: &Position, unit_b: &Position, heading_b: &Heading) -> Self {
|
pub fn new(unit_a: &Position, unit_b: &Position, heading_b: &Heading) -> Self {
|
||||||
let bearing = unit_a.get_bearing_to(unit_b);
|
let bearing = unit_a.get_bearing_to(unit_b);
|
||||||
let range = unit_a.distance_to_nmi(unit_b).round() as i32;
|
let range = unit_a.distance_to_nmi(unit_b).round() as i32;
|
||||||
let angels = unit_b.angels();
|
let angels = unit_b.angels();
|
||||||
let aspect = {
|
let aspect = {
|
||||||
let bearing = unit_b.get_bearing_to(unit_a);
|
let bearing = unit_b.get_bearing_to(unit_a);
|
||||||
let angle = (bearing - heading_b.0 as i32) % 360;
|
let angle = (bearing - heading_b.0 as i32) % 360;
|
||||||
if (45..135).contains(&angle) {
|
if (45..135).contains(&angle) {
|
||||||
Aspect::Flank
|
Aspect::Flank
|
||||||
} else if (135..225).contains(&angle) {
|
} else if (135..225).contains(&angle) {
|
||||||
Aspect::Cold
|
Aspect::Cold
|
||||||
} else if (225..315).contains(&angle) {
|
} else if (225..315).contains(&angle) {
|
||||||
Aspect::Flank
|
Aspect::Flank
|
||||||
} else if !(45..=315).contains(&angle) {
|
} else if !(45..=315).contains(&angle) {
|
||||||
Aspect::Hot
|
Aspect::Hot
|
||||||
} else {
|
} else {
|
||||||
Aspect::Cold
|
Aspect::Cold
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
bearing,
|
bearing,
|
||||||
range,
|
range,
|
||||||
angels,
|
angels,
|
||||||
aspect,
|
aspect,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_text(&self) -> String {
|
pub fn to_text(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"BRA {}° for {}NM at {} thousand {}",
|
"BRA {}° for {}NM at {} thousand {}",
|
||||||
self.bearing, self.range, self.angels, self.aspect
|
self.bearing, self.range, self.angels, self.aspect
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_voice(&self) -> String {
|
pub fn to_voice(&self) -> String {
|
||||||
let bearing = split(&self.bearing.to_string(), 1);
|
let bearing = split(&self.bearing.to_string(), 1);
|
||||||
let range = split(&self.range.to_string(), 1);
|
let range = split(&self.range.to_string(), 1);
|
||||||
format!(
|
format!(
|
||||||
"BRA . . . {} . . . {} . . . {} thousand . . . {}",
|
"BRA . . . {} . . . {} . . . {} thousand . . . {}",
|
||||||
bearing, range, self.angels, self.aspect
|
bearing, range, self.angels, self.aspect
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split(input: &str, n: usize) -> String {
|
fn split(input: &str, n: usize) -> String {
|
||||||
input
|
input
|
||||||
.chars()
|
.chars()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(i, c)| {
|
.flat_map(|(i, c)| {
|
||||||
if i != 0 && i % n == 0 {
|
if i != 0 && i % n == 0 {
|
||||||
Some(' ')
|
Some(' ')
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(std::iter::once(c))
|
.chain(std::iter::once(c))
|
||||||
})
|
})
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
|
|||||||
186
src/commands/bogeydope.rs
Normal file → Executable file
186
src/commands/bogeydope.rs
Normal file → Executable file
@@ -1,93 +1,93 @@
|
|||||||
use bevy::ecs::{
|
use bevy::ecs::{
|
||||||
query::{With, Without},
|
query::{With, Without},
|
||||||
system::{Commands, Query, Resource},
|
system::{Commands, Query, Resource},
|
||||||
};
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use guardian_commands::{ConsoleCommand, NamedCommand};
|
use guardian_commands::{ConsoleCommand, NamedCommand};
|
||||||
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::braa::Braa;
|
use crate::braa::Braa;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct BogeyDope;
|
pub struct BogeyDope;
|
||||||
|
|
||||||
impl NamedCommand for BogeyDope {
|
impl NamedCommand for BogeyDope {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"[shopping|bogey dope]"
|
"[shopping|bogey dope]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for BogeyDope {}
|
impl Resource for BogeyDope {}
|
||||||
|
|
||||||
pub fn bogey_dope(
|
pub fn bogey_dope(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cmd: ConsoleCommand<BogeyDope>,
|
mut cmd: ConsoleCommand<BogeyDope>,
|
||||||
awacs: Query<&Callsign>,
|
awacs: Query<&Callsign>,
|
||||||
player: Query<(&Callsign, &Position)>,
|
player: Query<(&Callsign, &Position)>,
|
||||||
npc: Query<(&Position, &Heading, &Velocity), (Without<Player>, With<Red>)>,
|
npc: Query<(&Position, &Heading, &Velocity), (Without<Player>, With<Red>)>,
|
||||||
) {
|
) {
|
||||||
let Some(Ok(BogeyDope)) = cmd.take() else {
|
let Some(Ok(BogeyDope)) = cmd.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(pilot) = &cmd.pilot else {
|
let Some(pilot) = &cmd.pilot else {
|
||||||
return; // no pilot
|
return; // no pilot
|
||||||
};
|
};
|
||||||
let Some(operator) = &cmd.operator else {
|
let Some(operator) = &cmd.operator else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok((p_callsign, p_pos)) = player.get(*pilot) else {
|
let Ok((p_callsign, p_pos)) = player.get(*pilot) else {
|
||||||
debug!("no player, died?");
|
debug!("no player, died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(a_callsign) = awacs.get(*operator) else {
|
let Ok(a_callsign) = awacs.get(*operator) else {
|
||||||
debug!("no awacs, they died?");
|
debug!("no awacs, they died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut distance: f64 = f64::MAX;
|
let mut distance: f64 = f64::MAX;
|
||||||
let mut n = (None, None);
|
let mut n = (None, None);
|
||||||
for (n_pos, n_heading, n_velocity) in npc.iter() {
|
for (n_pos, n_heading, n_velocity) in npc.iter() {
|
||||||
if n_velocity.speed() < 10.0 {
|
if n_velocity.speed() < 10.0 {
|
||||||
// ignore slow/on-ground units
|
// ignore slow/on-ground units
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let d = p_pos.distance_to_nmi(n_pos);
|
let d = p_pos.distance_to_nmi(n_pos);
|
||||||
if d < distance {
|
if d < distance {
|
||||||
distance = d;
|
distance = d;
|
||||||
n = (Some(n_pos), Some(n_heading));
|
n = (Some(n_pos), Some(n_heading));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (Some(n_position), Some(n_heading)) = n else {
|
let (Some(n_position), Some(n_heading)) = n else {
|
||||||
let message = format!("{}, {}, {}", p_callsign, a_callsign, "Skies are clear");
|
let message = format!("{}, {}, {}", p_callsign, a_callsign, "Skies are clear");
|
||||||
let voice_message = format!(
|
let voice_message = format!(
|
||||||
"{} . . . {} . . . {}",
|
"{} . . . {} . . . {}",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
a_callsign.to_voice(),
|
a_callsign.to_voice(),
|
||||||
"Skies are clear"
|
"Skies are clear"
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(TextMessage::new(message));
|
commands.entity(*pilot).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(*operator)
|
.entity(*operator)
|
||||||
.insert(VoiceMessage::new(voice_message));
|
.insert(VoiceMessage::new(voice_message));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let bra = Braa::new(p_pos, n_position, n_heading);
|
let bra = Braa::new(p_pos, n_position, n_heading);
|
||||||
let message = format!("{}, {}, {}", p_callsign, a_callsign, bra);
|
let message = format!("{}, {}, {}", p_callsign, a_callsign, bra);
|
||||||
let voice_message = format!(
|
let voice_message = format!(
|
||||||
"{} . . . {} . . . {}",
|
"{} . . . {} . . . {}",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
a_callsign.to_voice(),
|
a_callsign.to_voice(),
|
||||||
bra.to_voice()
|
bra.to_voice()
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(TextMessage::new(message));
|
commands.entity(*pilot).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(*operator)
|
.entity(*operator)
|
||||||
.insert(VoiceMessage::new(voice_message));
|
.insert(VoiceMessage::new(voice_message));
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/commands/cleartripwire.rs
Normal file → Executable file
120
src/commands/cleartripwire.rs
Normal file → Executable file
@@ -1,60 +1,60 @@
|
|||||||
use bevy::ecs::system::{Commands, Query, Resource};
|
use bevy::ecs::system::{Commands, Query, Resource};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use guardian_commands::{ConsoleCommand, NamedCommand};
|
use guardian_commands::{ConsoleCommand, NamedCommand};
|
||||||
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::tripwire::Tripwire;
|
use crate::tripwire::Tripwire;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct ClearTripwireCommand;
|
pub struct ClearTripwireCommand;
|
||||||
|
|
||||||
impl NamedCommand for ClearTripwireCommand {
|
impl NamedCommand for ClearTripwireCommand {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"clear [tripwire|warning]"
|
"clear [tripwire|warning]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for ClearTripwireCommand {}
|
impl Resource for ClearTripwireCommand {}
|
||||||
|
|
||||||
pub fn clear_tripwire(
|
pub fn clear_tripwire(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cmd: ConsoleCommand<ClearTripwireCommand>,
|
mut cmd: ConsoleCommand<ClearTripwireCommand>,
|
||||||
callsign: Query<&Callsign>,
|
callsign: Query<&Callsign>,
|
||||||
) {
|
) {
|
||||||
let Some(Ok(ClearTripwireCommand)) = cmd.take() else {
|
let Some(Ok(ClearTripwireCommand)) = cmd.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(pilot) = &cmd.pilot else {
|
let Some(pilot) = &cmd.pilot else {
|
||||||
debug!("no pilot");
|
debug!("no pilot");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(operator) = &cmd.operator else {
|
let Some(operator) = &cmd.operator else {
|
||||||
debug!("no operator");
|
debug!("no operator");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(p_callsign) = callsign.get(*pilot) else {
|
let Ok(p_callsign) = callsign.get(*pilot) else {
|
||||||
debug!("no player, died?");
|
debug!("no player, died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(a_callsign) = callsign.get(*operator) else {
|
let Ok(a_callsign) = callsign.get(*operator) else {
|
||||||
debug!("no awacs, they died?");
|
debug!("no awacs, they died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.entity(*pilot).remove::<Tripwire>();
|
commands.entity(*pilot).remove::<Tripwire>();
|
||||||
|
|
||||||
let message = format!("{}, {}, tripwire cleared", p_callsign, a_callsign);
|
let message = format!("{}, {}, tripwire cleared", p_callsign, a_callsign);
|
||||||
let message_voice = format!(
|
let message_voice = format!(
|
||||||
"{}, {}, tripwire cleared",
|
"{}, {}, tripwire cleared",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
a_callsign.to_voice(),
|
a_callsign.to_voice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(TextMessage::new(message));
|
commands.entity(*pilot).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(*operator)
|
.entity(*operator)
|
||||||
.insert(VoiceMessage::new(message_voice));
|
.insert(VoiceMessage::new(message_voice));
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/commands/mod.rs
Normal file → Executable file
46
src/commands/mod.rs
Normal file → Executable file
@@ -1,23 +1,23 @@
|
|||||||
use bevy::app::Plugin;
|
use bevy::app::Plugin;
|
||||||
use guardian_commands::AddConsoleCommand;
|
use guardian_commands::AddConsoleCommand;
|
||||||
|
|
||||||
mod bogeydope;
|
mod bogeydope;
|
||||||
mod cleartripwire;
|
mod cleartripwire;
|
||||||
mod radiocheck;
|
mod radiocheck;
|
||||||
mod tripwire;
|
mod tripwire;
|
||||||
|
|
||||||
use bogeydope::*;
|
use bogeydope::*;
|
||||||
use cleartripwire::*;
|
use cleartripwire::*;
|
||||||
use radiocheck::*;
|
use radiocheck::*;
|
||||||
use tripwire::*;
|
use tripwire::*;
|
||||||
pub struct CommandsPlugin;
|
pub struct CommandsPlugin;
|
||||||
|
|
||||||
impl Plugin for CommandsPlugin {
|
impl Plugin for CommandsPlugin {
|
||||||
fn build(&self, app: &mut bevy::prelude::App) {
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
app.add_plugins(guardian_commands::CommandsPlugin);
|
app.add_plugins(guardian_commands::CommandsPlugin);
|
||||||
app.add_console_command::<BogeyDope, _>(bogey_dope);
|
app.add_console_command::<BogeyDope, _>(bogey_dope);
|
||||||
app.add_console_command::<RadioCheck, _>(radio_check);
|
app.add_console_command::<RadioCheck, _>(radio_check);
|
||||||
app.add_console_command::<TripwireCommand, _>(set_tripwire);
|
app.add_console_command::<TripwireCommand, _>(set_tripwire);
|
||||||
app.add_console_command::<ClearTripwireCommand, _>(clear_tripwire);
|
app.add_console_command::<ClearTripwireCommand, _>(clear_tripwire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
src/commands/radiocheck.rs
Normal file → Executable file
114
src/commands/radiocheck.rs
Normal file → Executable file
@@ -1,57 +1,57 @@
|
|||||||
use bevy::ecs::system::{Commands, Query, Resource};
|
use bevy::ecs::system::{Commands, Query, Resource};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use guardian_commands::{ConsoleCommand, NamedCommand};
|
use guardian_commands::{ConsoleCommand, NamedCommand};
|
||||||
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct RadioCheck;
|
pub struct RadioCheck;
|
||||||
|
|
||||||
impl NamedCommand for RadioCheck {
|
impl NamedCommand for RadioCheck {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"radio check"
|
"radio check"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for RadioCheck {}
|
impl Resource for RadioCheck {}
|
||||||
|
|
||||||
pub fn radio_check(
|
pub fn radio_check(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cmd: ConsoleCommand<RadioCheck>,
|
mut cmd: ConsoleCommand<RadioCheck>,
|
||||||
callsign: Query<&Callsign>,
|
callsign: Query<&Callsign>,
|
||||||
) {
|
) {
|
||||||
let Some(Ok(RadioCheck)) = cmd.take() else {
|
let Some(Ok(RadioCheck)) = cmd.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(pilot) = &cmd.pilot else {
|
let Some(pilot) = &cmd.pilot else {
|
||||||
debug!("no pilot");
|
debug!("no pilot");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(operator) = &cmd.operator else {
|
let Some(operator) = &cmd.operator else {
|
||||||
debug!("no operator");
|
debug!("no operator");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(p_callsign) = callsign.get(*pilot) else {
|
let Ok(p_callsign) = callsign.get(*pilot) else {
|
||||||
debug!("no player, died?");
|
debug!("no player, died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(a_callsign) = callsign.get(*operator) else {
|
let Ok(a_callsign) = callsign.get(*operator) else {
|
||||||
debug!("no awacs, they died?");
|
debug!("no awacs, they died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = format!("{}, {}, {}", p_callsign, a_callsign, "five by five");
|
let message = format!("{}, {}, {}", p_callsign, a_callsign, "five by five");
|
||||||
let voice_message = format!(
|
let voice_message = format!(
|
||||||
"{} . . . {} . . . {}",
|
"{} . . . {} . . . {}",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
a_callsign.to_voice(),
|
a_callsign.to_voice(),
|
||||||
"five by five"
|
"five by five"
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(TextMessage::new(message));
|
commands.entity(*pilot).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(*operator)
|
.entity(*operator)
|
||||||
.insert(VoiceMessage::new(voice_message));
|
.insert(VoiceMessage::new(voice_message));
|
||||||
}
|
}
|
||||||
|
|||||||
206
src/commands/tripwire.rs
Normal file → Executable file
206
src/commands/tripwire.rs
Normal file → Executable file
@@ -1,103 +1,103 @@
|
|||||||
use bevy::ecs::system::{Commands, Query, Resource};
|
use bevy::ecs::system::{Commands, Query, Resource};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use guardian_commands::{ConsoleCommand, NamedCommand};
|
use guardian_commands::{ConsoleCommand, NamedCommand};
|
||||||
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
use guardian_core::{components::*, dcs::text::TextMessage, srs::voice::VoiceMessage};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::tripwire::Tripwire;
|
use crate::tripwire::Tripwire;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct TripwireCommand;
|
pub struct TripwireCommand;
|
||||||
|
|
||||||
impl NamedCommand for TripwireCommand {
|
impl NamedCommand for TripwireCommand {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"set [tripwire|warning]"
|
"set [tripwire|warning]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for TripwireCommand {}
|
impl Resource for TripwireCommand {}
|
||||||
|
|
||||||
pub fn set_tripwire(
|
pub fn set_tripwire(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cmd: ConsoleCommand<TripwireCommand>,
|
mut cmd: ConsoleCommand<TripwireCommand>,
|
||||||
callsign: Query<&Callsign>,
|
callsign: Query<&Callsign>,
|
||||||
) {
|
) {
|
||||||
let Some(Ok(TripwireCommand)) = cmd.take() else {
|
let Some(Ok(TripwireCommand)) = cmd.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(pilot) = &cmd.pilot else {
|
let Some(pilot) = &cmd.pilot else {
|
||||||
debug!("no pilot");
|
debug!("no pilot");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(operator) = &cmd.operator else {
|
let Some(operator) = &cmd.operator else {
|
||||||
debug!("no operator");
|
debug!("no operator");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(p_callsign) = callsign.get(*pilot) else {
|
let Ok(p_callsign) = callsign.get(*pilot) else {
|
||||||
debug!("no player, died?");
|
debug!("no player, died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(a_callsign) = callsign.get(*operator) else {
|
let Ok(a_callsign) = callsign.get(*operator) else {
|
||||||
debug!("no awacs, they died?");
|
debug!("no awacs, they died?");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(readback) = &cmd.raw else {
|
let Some(readback) = &cmd.raw else {
|
||||||
debug!("no readback");
|
debug!("no readback");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut parts = readback.split(' ');
|
let mut parts = readback.split(' ');
|
||||||
|
|
||||||
// check the last part, if it's a number, interpret as miles
|
// check the last part, if it's a number, interpret as miles
|
||||||
let Some(last) = parts.nth_back(0) else {
|
let Some(last) = parts.nth_back(0) else {
|
||||||
debug!("no last part");
|
debug!("no last part");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let distance = match last.parse::<i32>() {
|
let distance = match last.parse::<i32>() {
|
||||||
Ok(distance) => distance, // a number is found, interpret as miles
|
Ok(distance) => distance, // a number is found, interpret as miles
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let Some(maybe_number) = parts.nth_back(0) else {
|
let Some(maybe_number) = parts.nth_back(0) else {
|
||||||
// Report message garbled..
|
// Report message garbled..
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match maybe_number.parse::<i32>() {
|
match maybe_number.parse::<i32>() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if last == "kilometers" || last == "km" {
|
if last == "kilometers" || last == "km" {
|
||||||
(n as f64 * 0.621371).round() as i32
|
(n as f64 * 0.621371).round() as i32
|
||||||
} else {
|
} else {
|
||||||
n
|
n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// let message = format!("{}, {}. Unable to read, Say again.", caller, receiver);
|
// let message = format!("{}, {}. Unable to read, Say again.", caller, receiver);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// clamp distance between 0 and 120
|
// clamp distance between 0 and 120
|
||||||
let distance = distance.min(120).max(0);
|
let distance = distance.min(120).max(0);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(Tripwire::new(distance));
|
commands.entity(*pilot).insert(Tripwire::new(distance));
|
||||||
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"{}, {}, tripwire set to {} miles",
|
"{}, {}, tripwire set to {} miles",
|
||||||
p_callsign, a_callsign, distance
|
p_callsign, a_callsign, distance
|
||||||
);
|
);
|
||||||
let message_voice = format!(
|
let message_voice = format!(
|
||||||
"{}, {}, tripwire set to {} miles",
|
"{}, {}, tripwire set to {} miles",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
a_callsign.to_voice(),
|
a_callsign.to_voice(),
|
||||||
distance
|
distance
|
||||||
);
|
);
|
||||||
|
|
||||||
commands.entity(*pilot).insert(TextMessage::new(message));
|
commands.entity(*pilot).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(*operator)
|
.entity(*operator)
|
||||||
.insert(VoiceMessage::new(message_voice));
|
.insert(VoiceMessage::new(message_voice));
|
||||||
}
|
}
|
||||||
|
|||||||
0
src/config.rs
Normal file → Executable file
0
src/config.rs
Normal file → Executable file
0
src/main.rs
Normal file → Executable file
0
src/main.rs
Normal file → Executable file
182
src/tripwire.rs
Normal file → Executable file
182
src/tripwire.rs
Normal file → Executable file
@@ -1,91 +1,91 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
app::{Plugin, Update},
|
app::{Plugin, Update},
|
||||||
ecs::{
|
ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
query::{With, Without},
|
query::{With, Without},
|
||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use guardian_core::{
|
use guardian_core::{
|
||||||
components::*,
|
components::*,
|
||||||
dcs::text::TextMessage,
|
dcs::text::TextMessage,
|
||||||
srs::{voice::VoiceMessage, Radio, RadioInfo},
|
srs::{voice::VoiceMessage, Radio, RadioInfo},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::braa::Braa;
|
use crate::braa::Braa;
|
||||||
|
|
||||||
pub struct TripwirePlugin;
|
pub struct TripwirePlugin;
|
||||||
|
|
||||||
impl Plugin for TripwirePlugin {
|
impl Plugin for TripwirePlugin {
|
||||||
fn build(&self, app: &mut bevy::prelude::App) {
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
app.add_systems(Update, tripwire);
|
app.add_systems(Update, tripwire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tripwire(
|
fn tripwire(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
awacs: Query<(Entity, &Callsign, &Radio), (With<Awacs>, With<Blue>, With<Radio>)>,
|
awacs: Query<(Entity, &Callsign, &Radio), (With<Awacs>, With<Blue>, With<Radio>)>,
|
||||||
mut players: Query<(Entity, &Callsign, &Position, &RadioInfo, &mut Tripwire), With<Player>>,
|
mut players: Query<(Entity, &Callsign, &Position, &RadioInfo, &mut Tripwire), With<Player>>,
|
||||||
npc: Query<(&Id, &Position, &Heading, &Velocity), (Without<Player>, With<Red>)>,
|
npc: Query<(&Id, &Position, &Heading, &Velocity), (Without<Player>, With<Red>)>,
|
||||||
) {
|
) {
|
||||||
// get the players radio frequencies
|
// get the players radio frequencies
|
||||||
// match the tuned frequency with one of the awacs'
|
// match the tuned frequency with one of the awacs'
|
||||||
// broadcast message on that ferquency and as that awacs
|
// broadcast message on that ferquency and as that awacs
|
||||||
|
|
||||||
for (ent, awacs, radio) in awacs.iter() {
|
for (ent, awacs, radio) in awacs.iter() {
|
||||||
for (p_ent, p_callsign, p_position, radioinfo, mut p_tripwire) in players.iter_mut() {
|
for (p_ent, p_callsign, p_position, radioinfo, mut p_tripwire) in players.iter_mut() {
|
||||||
// check if user is on the same frequency as awacs
|
// check if user is on the same frequency as awacs
|
||||||
if !radioinfo
|
if !radioinfo
|
||||||
.radios
|
.radios
|
||||||
.iter()
|
.iter()
|
||||||
.any(|f| f.freq as u64 == radio.frequency && f.modulation == radio.modulation)
|
.any(|f| f.freq as u64 == radio.frequency && f.modulation == radio.modulation)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (n_id, n_position, n_heading, n_velocity) in npc.iter() {
|
for (n_id, n_position, n_heading, n_velocity) in npc.iter() {
|
||||||
if n_velocity.speed() < 10.0 {
|
if n_velocity.speed() < 10.0 {
|
||||||
// ignore slow/on-ground units
|
// ignore slow/on-ground units
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let distance = n_position.distance_to_nmi(p_position);
|
let distance = n_position.distance_to_nmi(p_position);
|
||||||
if distance <= p_tripwire.range && !p_tripwire.reported.contains(n_id) {
|
if distance <= p_tripwire.range && !p_tripwire.reported.contains(n_id) {
|
||||||
p_tripwire.reported.push(*n_id);
|
p_tripwire.reported.push(*n_id);
|
||||||
|
|
||||||
let bra = Braa::new(p_position, n_position, n_heading);
|
let bra = Braa::new(p_position, n_position, n_heading);
|
||||||
let message = format!("{}, {}, {}", p_callsign, awacs, bra);
|
let message = format!("{}, {}, {}", p_callsign, awacs, bra);
|
||||||
let voice_message = format!(
|
let voice_message = format!(
|
||||||
"{} . . . {} . . . {}",
|
"{} . . . {} . . . {}",
|
||||||
p_callsign.to_voice(),
|
p_callsign.to_voice(),
|
||||||
awacs.to_voice(),
|
awacs.to_voice(),
|
||||||
bra.to_voice()
|
bra.to_voice()
|
||||||
);
|
);
|
||||||
|
|
||||||
// commands.spawn((Id::new(player_id.into()), TextMessage::new(message)));
|
// commands.spawn((Id::new(player_id.into()), TextMessage::new(message)));
|
||||||
commands.entity(p_ent).insert(TextMessage::new(message));
|
commands.entity(p_ent).insert(TextMessage::new(message));
|
||||||
commands
|
commands
|
||||||
.entity(ent)
|
.entity(ent)
|
||||||
.insert(VoiceMessage::new(voice_message));
|
.insert(VoiceMessage::new(voice_message));
|
||||||
} else if distance > p_tripwire.range && p_tripwire.reported.contains(n_id) {
|
} else if distance > p_tripwire.range && p_tripwire.reported.contains(n_id) {
|
||||||
p_tripwire.reported.retain(|b| b != n_id);
|
p_tripwire.reported.retain(|b| b != n_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Tripwire {
|
pub struct Tripwire {
|
||||||
range: f64,
|
range: f64,
|
||||||
reported: Vec<Id>,
|
reported: Vec<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tripwire {
|
impl Tripwire {
|
||||||
pub fn new(distance: i32) -> Self {
|
pub fn new(distance: i32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
range: distance as f64,
|
range: distance as f64,
|
||||||
reported: vec![],
|
reported: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user