use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClientSettings {
#[serde(default = "default_version")]
pub version: String,
#[serde(default)]
pub keys: ClientKeyBindings,
}
fn default_version() -> String {
"1".into()
}
impl Default for ClientSettings {
fn default() -> Self {
Self {
version: default_version(),
keys: ClientKeyBindings::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClientKeyBindings {
#[serde(default = "default_target_t1")]
pub target_t1: String,
#[serde(default = "default_target_t2")]
pub target_t2: String,
#[serde(default = "default_auto_t1")]
pub auto_t1: String,
#[serde(default = "default_auto_t2")]
pub auto_t2: String,
#[serde(default = "default_loadout")]
pub loadout_menu: String,
#[serde(default = "default_rotation_editor")]
pub rotation_editor: String,
#[serde(default = "default_dodge")]
pub dodge: String,
#[serde(default = "default_lunge")]
pub lunge: String,
#[serde(default = "default_block")]
pub block: String,
#[serde(default = "default_clear_t1")]
pub clear_target_t1: String,
#[serde(default = "default_clear_t2")]
pub clear_target_t2: String,
#[serde(default = "default_hotbar")]
pub hotbar: Vec<Option<String>>,
}
fn default_target_t1() -> String {
"Tab".into()
}
fn default_target_t2() -> String {
"Shift+Tab".into()
}
fn default_auto_t1() -> String {
"r".into()
}
fn default_auto_t2() -> String {
"Shift+r".into()
}
fn default_loadout() -> String {
"l".into()
}
fn default_rotation_editor() -> String {
"o".into()
}
fn default_dodge() -> String {
"c".into()
}
fn default_lunge() -> String {
"Shift+Space".into()
}
fn default_block() -> String {
"q".into()
}
fn default_clear_t1() -> String {
"0".into()
}
fn default_clear_t2() -> String {
"Shift+0".into()
}
fn default_hotbar() -> Vec<Option<String>> {
Vec::new()
}
impl Default for ClientKeyBindings {
fn default() -> Self {
Self {
target_t1: default_target_t1(),
target_t2: default_target_t2(),
auto_t1: default_auto_t1(),
auto_t2: default_auto_t2(),
loadout_menu: default_loadout(),
rotation_editor: default_rotation_editor(),
dodge: default_dodge(),
lunge: default_lunge(),
block: default_block(),
clear_target_t1: default_clear_t1(),
clear_target_t2: default_clear_t2(),
hotbar: default_hotbar(),
}
}
}
impl ClientKeyBindings {
pub fn hotbar_ability(&self, slot_1_to_9: u8) -> Option<&str> {
if !(1..=9).contains(&slot_1_to_9) {
return None;
}
self.hotbar
.get((slot_1_to_9 - 1) as usize)
.and_then(|a| a.as_deref())
.filter(|id| !id.is_empty())
}
}
fn find_settings_path() -> PathBuf {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
for ancestor in manifest.ancestors() {
let candidate = ancestor.join("assets/config/client-settings.yaml");
if candidate.is_file() {
return candidate;
}
}
PathBuf::from("assets/config/client-settings.yaml")
}
pub fn load_client_settings() -> ClientSettings {
let path = find_settings_path();
let Ok(raw) = std::fs::read_to_string(&path) else {
return ClientSettings::default();
};
serde_yaml::from_str(&raw).unwrap_or_default()
}