28个稳定版本

1.10.5 2023年9月12日
1.9.3 2023年1月23日
1.8.2 2022年12月9日
1.8.1 2022年11月13日
1.3.2 2021年12月31日

#99算法 分类中

每月 39次下载

MIT 许可证

150KB
3.5K SLoC

arma-rs

加入arma-rs Discord! codecov

制作Arma 3扩展的最佳方式。

使用方法

[dependencies]
arma-rs = "1.10.4"

[lib]
name = "my_extension"
crate-type = ["cdylib"]

Hello World

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)
}
"my_extension" callExtension ["hello", []]; // Returns ["Hello", 0, 0]
"my_extension" callExtension ["welcome", ["John"]]; // Returns ["Welcome John", 0, 0]

命令分组

命令可以分组,使大型项目管理更加容易。

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

#[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)
    }
}

使用格式 group:command 调用命令分组。您可以根据需要嵌套分组。

"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]

回调函数

扩展回调可以在扩展中的任何地方通过在处理器开头添加类型为 Context 的变量来调用。

use arma_rs::Context;

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

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

调用上下文

从Arma v2.11开始,每次调用扩展时都提供了额外的上下文。可以通过可选的 Context 参数访问此上下文。

use arma_rs::Context;

pub fn call_context(ctx: Context) -> String {
    format!(
        "{:?},{:?},{:?},{:?}",
        ctx.caller(),
        ctx.source(),
        ctx.mission(),
        ctx.server()
    )
}

pub fn group() -> arma_rs::Group {
    arma_rs::Group::new().command("call_context", call_context)
}

可以通过使用 call-context 功能标志来切换对此上下文的支持,该标志默认启用。

持久状态

扩展和命令分组都允许使用基于类型的持久状态值,每个类型最多一个实例。然后可以通过可选的 Context 参数访问这些状态值。

全局状态

扩展状态可以从任何命令处理器访问。

use arma_rs::{arma, Context, ContextState, Extension};

use std::sync::atomic::{AtomicU32, Ordering};

#[arma]
fn init() -> Extension {
    Extension::build()
        .command("counter_increment", increment)
        .state(AtomicU32::new(0))
        .finish()
}

pub fn increment(ctx: Context) -> Result<(), ()> {
    let Some(counter) = ctx.global().get::<AtomicU32>() else {
        return Err(());
    };
    counter.fetch_add(1, Ordering::SeqCst);
    Ok(())
}

分组状态

命令分组状态只能从同一组内的命令处理器访问。

use arma_rs::{Context, ContextState, Extension};

use std::sync::atomic::{AtomicU32, Ordering};

pub fn increment(ctx: Context) -> Result<(), ()> {
    let Some(counter) = ctx.group().get::<AtomicU32>() else {
        return Err(());
    };
    counter.fetch_add(1, Ordering::SeqCst);
    Ok(())
}

pub fn group() -> arma_rs::Group {
    arma_rs::Group::new()
        .command("increment", increment)
        .state(AtomicU32::new(0))
}

自定义返回类型

如果您正在携带带有自己类型的现有Rust库,可以轻松定义它们如何转换为Arma。

use arma_rs::{IntoArma, Value};

#[derive(Default)]
pub struct MemoryReport {
    total: u64,
    free: u64,
    avail: u64,
}

impl IntoArma for MemoryReport {
    fn to_arma(&self) -> Value {
        Value::Array(
            vec![self.total, self.free, self.avail]
                .into_iter()
                .map(|v| v.to_string().to_arma())
                .collect(),
        )
    }
}

错误代码

默认情况下,arma-rs将仅允许通过 RvExtensionArgs 发送命令。使用仅带有函数名称的 callExtension 将返回空字符串。

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

可以通过在构建扩展时调用 .allow_no_args() 来更改此行为。建议不要使用此功能,而是实现错误处理。

代码 描述
0 成功
1 找不到命令
2x 无效的参数数量,x 是接收到的计数
3x 无效的参数类型,x 是参数位置
4 尝试写入比缓冲区更大的值
9 应用程序错误,来自使用 Result

错误示例

use arma_rs::Context;

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<String, String> {
  if error {
    Err(String::from("told to error"))
  } else {
    Ok(String::from("told to succeed"))
  }
}
"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]

测试

可以通过使用 extension.call() 方法创建测试。

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

    #[test]
    fn welcome() {
        let extension = init().testing();
        let (output, _) =
            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) = 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);
    }
}

单元配置数组

arma-rs 包含一个 配置模块,以帮助处理 Arma 的单元配置数组

use arma_rs::{FromArma, loadout::{Loadout, InventoryItem, Weapon, Magazine}};

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));

常见的 Rust 库

arma-rs 支持一些常见的 Rust 库。您可以通过将它们的名称添加到 arma-rs 的功能中启用它们的支持。

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

如果您想添加对新库的支持,请首先创建一个问题。

chrono

crates.io

chrono - 转换为 Arma

NaiveDateTimeDateTime<TimeZone> 将转换为 Arma 的日期数组。时区将始终转换为 UTC。

chrono - 从 Arma 转换

Arma 的日期数组可以转换为 NaiveDateTime

uuid

crates.io

uuid - 转换为 Arma

Uuid 将转换为字符串。

serde_json

crates.io

serde_json - 转换为 Arma

任何 serde_json::Value 变体会转换为适当的 Arma 类型。

为 x86 (32 位) 构建

rustup toolchain install stable-i686-pc-windows-msvc
cargo +stable-i686-pc-windows-msvc build

贡献

欢迎提交拉取请求。对于重大更改,请先打开一个问题来讨论您想进行哪些更改。

依赖项

~0.4–40MB
~570K SLoC