arma-rs

Join the arma-rs Discord! codecov

The best way to make Arma 3 Extensions.

Usage

toml [dependencies] arma-rs = "1.7.4"

Hello World

```rust use arma_rs::{arma, Extension};

[arma]

fn init() -> Extension { Extension::build() .command("hello", hello) .command("welcome", welcome) .finish() }

pub fn hello() -> &'static str { "Hello" }

pub fn welcome(name: String) -> String { format!("Welcome {}", name) } ```

sqf "my_extension" callExtension ["hello", []]; // Returns ["Hello", 0, 0] "my_extension" callExtension ["welcome", ["John"]]; // Returns ["Welcome John", 0, 0]

Command Groups

Commands can be grouped together, making your large projects much easier to manage.

```rust use arma_rs::{arma, Extension, Group};

mod system_info; mod timer;

[arma]

fn init() -> Extension { Extension::build() .group("hello", Group::new() .command("english", hello::english) .group("english", Group::new() .command("casual", hello::english_casual) ) .command("french", hello::french), ) .group("welcome", Group::new() .command("english", welcome::english) .command("french", welcome::french), ) .finish() }

mod hello { pub fn english() -> &'static str { "Hello" } pub fn english_casual() -> &'static str { "Hey" } pub fn french() -> &'static str { "Bonjour" } }

mod welcome { pub fn english(name: String) -> String { format!("Welcome {}", name) } pub fn french(name: String) -> String { format!("Bienvenue {}", name) } } ```

Commands groups are called by using the format group:command. You can nest groups as much as you want.

sqf "my_extension" callExtension ["hello:english", []]; // Returns ["Hello", 0, 0] "my_extension" callExtension ["hello:english:casual", []]; // Returns ["Hey", 0, 0] "my_extension" callExtension ["hello:french", []]; // Returns ["Bonjour", 0, 0]

Callbacks

Extension callbacks can be invoked anywhere in the extension by adding a variable of type Context to the start of a handler.

```rust use arma_rs::Context;

pub fn sleep(ctx: Context, duration: u64, id: String) { std::thread::spawn(move || { std::thread::sleep(std::time::Duration::fromsecs(duration)); ctx.callback("exampletimer", "done", Some(id)); }); }

pub fn group() -> armars::Group { armars::Group::new().command("sleep", sleep) } ```

Custom Return Types

If you're bringing your existing Rust library with your own types, you can easily define how they are converted to Arma.

```rust

[derive(Default)]

pub struct MemoryReport { total: u64, free: u64, avail: u64, }

impl IntoArma for MemoryReport { fn toarma(&self) -> ArmaValue { ArmaValue::Array( vec![self.total, self.free, self.avail] .intoiter() .map(|v| v.tostring().toarma()) .collect(), ) } } ```

Error Codes

By default arma-rs will only allow commands via RvExtensionArgs. Using callExtension with only a function name will return an empty string.

sqf "my_extension" callExtension "hello:english" // returns "" "my_extension" callExtension ["hello:english", []] // returns ["Hello", 0, 0]

This behvaiour can be changed by calling .allow_no_args() when building the extension. It is recommended not to use this, and to implement error handling instead.

| Code | Description | |------|---------------------------------------------------| | 0 | Success | | 1 | Command not found | | 2x | Invalid argument count, x is received count | | 3x | Invalid argument type, x is argument position | | 4 | Attempted to write a value larger than the buffer | | 9 | Application error, from using a Result |

Error Examples

```rust pub fn add(a: i32, b: i32) -> i32 { a + b }

pub fn overflow(ctx: Context) -> String { "X".repeat(ctx.buffer_len() + 1) }

pub fn should_error(error: bool) -> Result { if error { Err(String::from("told to error") } else { Ok(String::from("told to succeed") } } ```

sqf "my_extension" callExtension ["add", [1, 2]]; // Returns ["3", 0, 0] "my_extension" callExtension ["sub", [1, 2]]; // Returns ["", 1, 0] "my_extension" callExtension ["add", [1, 2, 3]]; // Returns ["", 23, 0], didn't expect 3 elements "my_extension" callExtension ["add", [1, "two"]]; // Returns ["", 31, 0], unable to parse the second argument "my_extension" callExtension ["overflow", []]; // Returns ["", 4, 0], the return size was larger than the buffer "my_extension" callExtension ["should_error", [true]]; // Returns ["told to error", 9, 0] "my_extension" callExtension ["should_error", [false]]; // Returns ["told to succeed", 0, 0]

Testing

Tests can be created utilizing the extension.call() method.

```rust

[cfg(test)]

mod tests { use super::init;

#[test]
fn hello() {
    let extension = init().testing();
    let (output, _) = unsafe { extension.call("hello:english", None) };
    assert_eq!(output, "hello");
}

#[test]
fn welcome() {
    let extension = init().testing();
    let (output, _) =
        unsafe { extension.call("welcome:english", Some(vec!["John".to_string()])) };
    assert_eq!(output, "Welcome John");
}

#[test]
fn sleep_1sec() {
    let extension = Extension::build()
        .group("timer", super::group())
        .finish()
        .testing();
    let (_, code) = unsafe {
        extension.call(
            "timer:sleep",
            Some(vec!["1".to_string(), "test".to_string()]),
        )
    };
    assert_eq!(code, 0);
    let result = extension.callback_handler(
        |name, func, data| {
            assert_eq!(name, "timer:sleep");
            assert_eq!(func, "done");
            if let Some(Value::String(s)) = data {
                Result::Ok(s)
            } else {
                Result::Err("Data was not a string".to_string())
            }
        },
        Duration::from_secs(2),
    );
    assert_eq!(Result::Ok("test".to_string()), result);
}

} ```

Unit Loadout Array

arma-rs includes a loadout module to assist with the handling of Arma's Unit Loadout Array.

rs let l = r#"[[],[],[],["U_Marshal",[]],[],[],"H_Cap_headphones","G_Aviator",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]"#; let mut loadout = Loadout::from_arma(l.to_string()).unwrap(); loadout.set_secondary({ let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string()); weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1)); weapon }); loadout.set_primary({ let mut weapon = Weapon::new("arifle_MXC_F".to_string()); weapon.set_optic("optic_Holosight".to_string()); weapon }); let uniform = loadout.uniform_mut(); uniform.set_class("U_B_CombatUniform_mcam".to_string()); let uniform_items = uniform.items_mut().unwrap(); uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 3)); uniform_items.push(InventoryItem::new_magazine("30Rnd_65x39_caseless_mag".to_string(), 5, 30));

Common Rust Libraries

arma-rs supports some common Rust libraries. You can enable their support by adding their name to the features of arma-rs.

toml arma-rs = { version = "1.7.0", features = ["chrono"] }

Please create an issue first if you would like to add support for a new library.

chrono

crates.io

chrono - Convert to Arma

NaiveDateTime and DateTime<TimeZone> will be converted to Arma's date array. The timezone will always be converted to UTC.

chrono - Convert From Arma

Arma's date array can be converted to NaiveDateTime.

uuid

crates.io

uuid - Convert To Arma

Uuid will be converted to a string.

serde_json

crates.io

serde_json - Convert To Arma

Any variant of serde_json::Value will be converted to the appropriate Arma type.

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.