use autocxx::prelude::*;
use std::collections::HashMap;
use crate::sdk;
macro_rules! config_nodes {
(
$enum_name:ident [ $(
$type_name:ident $( : $rust_type:ty )?,
)+ ],
$type_mod_name:ident
) => {
paste::paste! {
#[derive(Debug)]
pub enum $enum_name { $(
$type_name $( ($rust_type) )?,
)+ }
impl $enum_name { $( $(
pub fn [<as_ $type_name:snake>](self) -> Option<$rust_type> {
match self {
$enum_name::$type_name(v) => Some(v),
$enum_name::None => None,
_ => panic!("Expected {}", stringify!($type_name))
}
}
)? )+ }
mod $type_mod_name {
use super::*;
$( $(
#[allow(dead_code)]
pub(super) type $type_name = $rust_type;
)? )+
}
}
};
}
config_nodes!(ConfigNode [
Bool: bool,
String: std::string::String,
F64: f64,
List: Vec<ConfigNode>,
Dict: HashMap<String, ConfigNode>,
None,
], config_type);
type ConfigNodePtr = UniquePtr<sdk::Config::Value_ValuePtr>;
pub(crate) unsafe fn read_config_node(node: ConfigNodePtr) -> ConfigNode {
let node_type = sdk::config_node::get_type(node.as_ref().unwrap());
let node_type = altv_sdk::ConfigValueType::try_from(node_type)
.unwrap_or_else(|_| panic!("unknown config value type: {node_type}"));
{
use altv_sdk::ConfigValueType;
use sdk::config_node::*;
match node_type {
ConfigValueType::Bool => ConfigNode::Bool(read_bool(node)),
ConfigValueType::String => ConfigNode::String(read_string(node).to_string()),
ConfigValueType::Number => ConfigNode::F64(read_f64(node)),
ConfigValueType::List => {
let raw = read_list(node);
let vec = raw
.into_iter()
.map(|node| read_config_node(copy_value_ptr(node)))
.collect();
ConfigNode::List(vec)
}
ConfigValueType::Dict => {
let raw = read_dict(node);
let entries = raw.into_iter().map(|pair| {
let key = read_dict_pair_key(pair).to_string();
let value = {
let raw = read_dict_pair_value(pair);
read_config_node(raw)
};
(key, value)
});
let dict = HashMap::from_iter(entries);
ConfigNode::Dict(dict)
}
ConfigValueType::None => ConfigNode::None,
}
}
}
pub trait ConfigRootNodeStore {
fn root_node(&self) -> &config_type::Dict;
}
pub trait Config: ConfigRootNodeStore {
fn get_custom_key(&self, key: &str) -> Option<&ConfigNode> {
self.root_node().get(key)
}
}
#[derive(Debug)]
pub struct ServerConfig {
pub modules: Vec<String>,
pub resources: Vec<String>,
pub name: Option<String>,
pub host: Option<String>,
pub port: Option<i32>,
pub players: Option<i32>,
pub password: Option<String>,
pub announce: Option<bool>,
pub token: Option<String>,
pub gamemode: Option<String>,
pub website: Option<String>,
pub language: Option<String>,
pub description: Option<String>,
pub debug: Option<bool>,
pub streaming_distance: Option<f64>,
pub migration_distance: Option<f64>,
pub timeout: Option<f64>,
pub announce_retry_error_delay: Option<i32>,
pub announce_retry_error_attempts: Option<i32>,
pub duplicate_players: Option<i32>,
pub map_bounds_min_x: Option<f64>,
pub map_bounds_min_y: Option<f64>,
pub map_bounds_max_x: Option<f64>,
pub map_bounds_max_y: Option<f64>,
pub map_cell_area_size: Option<f64>,
pub col_shape_tick_rate: Option<i32>,
pub log_streams: Option<Vec<String>>,
pub entity_worker_count: Option<i32>,
pub tags: Option<Vec<String>>,
pub connection_queue: Option<bool>,
pub use_early_auth: Option<bool>,
pub early_auth_url: Option<String>,
pub use_cdn: Option<bool>,
pub cdn_url: Option<String>,
pub send_player_names: Option<bool>,
pub spawn_after_connect: Option<bool>,
pub world_profiler: Option<config_type::Dict>,
pub js_module: Option<config_type::Dict>,
pub csharp_module: Option<config_type::Dict>,
pub voice: Option<config_type::Dict>,
root_node: config_type::Dict,
}
impl ServerConfig {
pub fn get() -> Self {
let node_ptr = unsafe { sdk::ICore::GetServerConfig() };
let config = unsafe { read_config_node(node_ptr) };
let mut config = config.as_dict().unwrap_or_else(|| {
panic!("Server config node must be of type dict");
});
Self {
modules: get_string_list_required(config.remove("modules").unwrap()),
resources: get_string_list_required(config.remove("resources").unwrap()),
name: get_string(config.remove("name")),
host: get_string(config.remove("host")),
port: get_i32(config.remove("port")),
players: get_i32(config.remove("players")),
password: get_string(config.remove("password")),
announce: get_bool(config.remove("announce")),
announce_retry_error_attempts: get_i32(config.remove("announceRetryErrorAttempts")),
announce_retry_error_delay: get_i32(config.remove("announceRetryErrorDelay")),
token: get_string(config.remove("token")),
gamemode: get_string(config.remove("gamemode")),
website: get_string(config.remove("website")),
language: get_string(config.remove("language")),
description: get_string(config.remove("description")),
debug: get_bool(config.remove("debug")),
streaming_distance: get_f64(config.remove("streamingDistance")),
migration_distance: get_f64(config.remove("migrationDistance")),
timeout: get_f64(config.remove("timeout")),
duplicate_players: get_i32(config.remove("duplicatePlayers")),
map_bounds_min_x: get_f64(config.remove("mapBoundsMinX")),
map_bounds_min_y: get_f64(config.remove("mapBoundsMinY")),
map_bounds_max_x: get_f64(config.remove("mapBoundsMaxX")),
map_bounds_max_y: get_f64(config.remove("mapBoundsMaxY")),
map_cell_area_size: get_f64(config.remove("mapCellAreaSize")),
col_shape_tick_rate: get_i32(config.remove("colShapeTickRate")),
log_streams: get_string_list(config.remove("logStreams")),
entity_worker_count: get_i32(config.remove("entityWorkerCount")),
tags: get_string_list(config.remove("tags")),
connection_queue: get_bool(config.remove("connectionQueue")),
use_early_auth: get_bool(config.remove("useEarlyAuth")),
early_auth_url: get_string(config.remove("earlyAuthUrl")),
use_cdn: get_bool(config.remove("useCdn")),
cdn_url: get_string(config.remove("cdnUrl")),
send_player_names: get_bool(config.remove("sendPlayerNames")),
spawn_after_connect: get_bool(config.remove("spawnAfterConnect")),
world_profiler: get_dict(config.remove("worldProfiler")),
js_module: get_dict(config.remove("js-module")),
csharp_module: get_dict(config.remove("csharp-module")),
voice: get_dict(config.remove("voice")),
root_node: config,
}
}
}
impl ConfigRootNodeStore for ServerConfig {
fn root_node(&self) -> &config_type::Dict {
&self.root_node
}
}
impl Config for ServerConfig {}
#[derive(Debug)]
pub struct ResourceConfig {
root_node: config_type::Dict,
}
impl ResourceConfig {
pub(crate) fn new(node_ptr: ConfigNodePtr) -> Self {
let config = unsafe { read_config_node(node_ptr) };
let config = config.as_dict().unwrap_or_else(|| {
panic!("Resource config node must be of type dict");
});
Self { root_node: config }
}
}
impl ConfigRootNodeStore for ResourceConfig {
fn root_node(&self) -> &config_type::Dict {
&self.root_node
}
}
impl Config for ResourceConfig {}
fn get_string_list_required(node: ConfigNode) -> Vec<String> {
let list = node.as_list().unwrap();
list.into_iter().map(|v| v.as_string().unwrap()).collect()
}
fn get_string_list(node: Option<ConfigNode>) -> Option<Vec<String>> {
let Some(list) = node else {
return None;
};
let list = list.as_list().unwrap();
Some(list.into_iter().map(|v| v.as_string().unwrap()).collect())
}
fn get_string(node: Option<ConfigNode>) -> Option<String> {
node.map(|v| v.as_string().unwrap())
}
fn get_i32(node: Option<ConfigNode>) -> Option<i32> {
node.map(|v| v.as_f64().unwrap() as i32)
}
fn get_f64(node: Option<ConfigNode>) -> Option<f64> {
node.map(|v| v.as_f64().unwrap())
}
fn get_bool(node: Option<ConfigNode>) -> Option<bool> {
node.map(|v| v.as_bool().unwrap())
}
fn get_dict(node: Option<ConfigNode>) -> Option<config_type::Dict> {
node.map(|v| v.as_dict().unwrap())
}