mirror of
https://github.com/Corfirean/coa-server-manager.git
synced 2026-11-11 03:51:01 +00:00
feat(races): synchronize custom races bot setting with playerbots excluded races
This commit is contained in:
1 parent
94d4ade1c7
commit
4c4d39f1de
4 files changed
+164
-7
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
area: manager
|
||||
type: fixed
|
||||
audience: admins
|
||||
title: Custom races bot setting synchronizes Playerbots excluded race lists
|
||||
---
|
||||
The "Bots can be custom races" setting now configures AiPlayerbot.ExcludedBotRaces in playerbots.conf. Off excludes all added races (recommended), while on allows stable added races (experimental).
|
||||
@@ -54,6 +54,85 @@ pub const RACE_MPQS: &[&str] = &[
|
||||
"patch-ZZR.MPQ",
|
||||
];
|
||||
|
||||
pub const EXCLUDED_BOT_RACES_RECOMMENDED: &str =
|
||||
"19,27,65,72,77,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128";
|
||||
|
||||
pub const EXCLUDED_BOT_RACES_EXPERIMENTAL: &str =
|
||||
"19,27,65,72,77,16,50,67,32,66,68,69,70,71,74,52,53,80,81";
|
||||
|
||||
/// Finds all active playerbots.conf files on this server (root, coa-bots folder, and secondary realm).
|
||||
pub fn playerbots_conf_paths(root: &Path) -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
let candidates = [
|
||||
root.join("Core/configs/modules/playerbots.conf"),
|
||||
root.join("coa-bots/Core/configs/modules/playerbots.conf"),
|
||||
root.join("coa-bots/core/configs/modules/playerbots.conf"),
|
||||
crate::multiworld::root(root).join("Core/configs/modules/playerbots.conf"),
|
||||
];
|
||||
for path in candidates {
|
||||
if path.is_file() {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
if paths.is_empty() {
|
||||
for dist in [
|
||||
root.join("Core/configs/modules/playerbots.conf.dist"),
|
||||
root.join("coa-bots/Core/configs/modules/playerbots.conf.dist"),
|
||||
] {
|
||||
if dist.is_file() {
|
||||
let target = dist.with_extension("");
|
||||
if fs::copy(&dist, &target).is_ok() {
|
||||
paths.push(target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
/// Checks whether playerbots are configured to allow custom races.
|
||||
/// Returns true if AiPlayerbot.ExcludedBotRaces exists and does not exclude general custom races.
|
||||
pub fn is_bot_custom_races_allowed(root: &Path) -> Option<bool> {
|
||||
for path in playerbots_conf_paths(root) {
|
||||
if let Ok(bytes) = fs::read(&path) {
|
||||
if let Ok(conf) = crate::config::parser::ConfFile::parse_bytes(&bytes) {
|
||||
if let Some(val) = conf.get("AiPlayerbot.ExcludedBotRaces") {
|
||||
let clean = val.trim().trim_matches(['"', '\'']);
|
||||
return Some(!clean.contains("33") && !clean.contains("128"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Sets AiPlayerbot.ExcludedBotRaces in all discovered playerbots.conf files.
|
||||
pub fn sync_bot_races_config(root: &Path, allow_custom_races: bool) -> Result<()> {
|
||||
let excluded = if allow_custom_races {
|
||||
EXCLUDED_BOT_RACES_EXPERIMENTAL
|
||||
} else {
|
||||
EXCLUDED_BOT_RACES_RECOMMENDED
|
||||
};
|
||||
for path in playerbots_conf_paths(root) {
|
||||
if let Ok(bytes) = fs::read(&path) {
|
||||
if let Ok(mut conf) = crate::config::parser::ConfFile::parse_bytes(&bytes) {
|
||||
conf.set(
|
||||
"AiPlayerbot.ExcludedBotRaces",
|
||||
&format!("\"{excluded}\""),
|
||||
&[
|
||||
"CoA Custom: races new random bots never get",
|
||||
"Off (recommended): excludes all added races",
|
||||
"On (experimental): allows stable added races",
|
||||
],
|
||||
);
|
||||
fsx::atomic_write(&path, conf.to_text().as_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ClientPatchState {
|
||||
|
||||
@@ -214,6 +214,16 @@ pub fn set_enabled(root: &Path, meta: &Path, id: &str, on: bool) -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !on {
|
||||
let _ = crate::custom_races::sync_bot_races_config(root, false);
|
||||
} else {
|
||||
let allow = crate::custom_races::is_bot_custom_races_allowed(root).unwrap_or_else(|| {
|
||||
read(&dir(root).join("coa_custom_races.conf"))
|
||||
.and_then(|c| c.get("CoACustomRaces.BotsEnable").map(truthy))
|
||||
.unwrap_or(false)
|
||||
});
|
||||
let _ = crate::custom_races::sync_bot_races_config(root, allow);
|
||||
}
|
||||
}
|
||||
set_enabled_in(&catalog(), root, meta, id, on)
|
||||
}
|
||||
@@ -290,14 +300,27 @@ fn settings_in(cat: &[Entry], root: &Path, id: &str) -> Result<Vec<Setting>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
let bot_custom_races_allowed = if id == "custom-races" {
|
||||
crate::custom_races::is_bot_custom_races_allowed(root)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(effective
|
||||
.entries()
|
||||
.map(|(k, v)| Setting {
|
||||
key: k.to_string(),
|
||||
value: fields.get(k).and_then(|field| field.default_if_missing.as_ref()).filter(|_| active.get(k).is_none()).and_then(crate::squid::scalar).unwrap_or_else(|| v.to_string()),
|
||||
default: fields.get(k).and_then(|field| crate::squid::scalar(&field.default)).or_else(|| dist.as_ref().and_then(|d| d.get(k)).map(str::to_string)),
|
||||
field: fields.get(k).cloned(),
|
||||
doc: dist.as_ref().map(|d| d.doc_for(k)).filter(|d| !d.is_empty()).unwrap_or_else(|| active.doc_for(k)).join(" "),
|
||||
.map(|(k, v)| {
|
||||
let mut val = fields.get(k).and_then(|field| field.default_if_missing.as_ref()).filter(|_| active.get(k).is_none()).and_then(crate::squid::scalar).unwrap_or_else(|| v.to_string());
|
||||
if k == "CoACustomRaces.BotsEnable" {
|
||||
if let Some(allowed) = bot_custom_races_allowed {
|
||||
val = if allowed { "1".into() } else { "0".into() };
|
||||
}
|
||||
}
|
||||
Setting {
|
||||
key: k.to_string(),
|
||||
value: val,
|
||||
default: fields.get(k).and_then(|field| crate::squid::scalar(&field.default)).or_else(|| dist.as_ref().and_then(|d| d.get(k)).map(str::to_string)),
|
||||
field: fields.get(k).cloned(),
|
||||
doc: dist.as_ref().map(|d| d.doc_for(k)).filter(|d| !d.is_empty()).unwrap_or_else(|| active.doc_for(k)).join(" "),
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -353,6 +376,12 @@ fn save_settings_in(cat: &[Entry], root: &Path, meta: &Path, id: &str, changes:
|
||||
changed.push((key.clone(), value.to_string()));
|
||||
}
|
||||
}
|
||||
if id == "custom-races" {
|
||||
if let Some(bots_enable) = changes.get("CoACustomRaces.BotsEnable") {
|
||||
let allow = truthy(bots_enable);
|
||||
crate::custom_races::sync_bot_races_config(root, allow)?;
|
||||
}
|
||||
}
|
||||
if changed.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
@@ -672,4 +701,39 @@ mod tests {
|
||||
assert!(fs::read_to_string(dir(&root).join("war_games.conf")).unwrap().contains("WarGames.Enable = 0"));
|
||||
assert!(!list_in(&cat, &root).into_iter().find(|m| m.id == "war-games").unwrap().enabled);
|
||||
}
|
||||
#[test]
|
||||
fn custom_races_bot_settings_synchronize_playerbots_conf() {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let (root, meta) = server(d.path());
|
||||
let cat = catalog();
|
||||
fs::write(dir(&root).join("coa_custom_races.conf.dist"), "CoACustomRaces.Enable = 1\nCoACustomRaces.BotsEnable = 0\n").unwrap();
|
||||
fs::write(dir(&root).join("coa_custom_races.conf"), "CoACustomRaces.Enable = 1\nCoACustomRaces.BotsEnable = 0\n").unwrap();
|
||||
fs::write(dir(&root).join("playerbots.conf"), "AiPlayerbot.Enabled = 1\n").unwrap();
|
||||
|
||||
// 1. Initially CoACustomRaces.BotsEnable is 0
|
||||
let s = settings_in(&cat, &root, "custom-races").unwrap();
|
||||
assert_eq!(s.iter().find(|s| s.key == "CoACustomRaces.BotsEnable").unwrap().value, "0");
|
||||
|
||||
// 2. Enable bots for custom races: should write experimental exclusions to playerbots.conf
|
||||
let change_on = BTreeMap::from([("CoACustomRaces.BotsEnable".into(), "1".into())]);
|
||||
assert_eq!(save_settings_in(&cat, &root, &meta, "custom-races", &change_on).unwrap(), ["CoACustomRaces.BotsEnable"]);
|
||||
let pb = fs::read_to_string(dir(&root).join("playerbots.conf")).unwrap();
|
||||
assert!(pb.contains("AiPlayerbot.ExcludedBotRaces"));
|
||||
assert!(pb.contains(crate::custom_races::EXCLUDED_BOT_RACES_EXPERIMENTAL));
|
||||
|
||||
// Settings view now sees "1"
|
||||
let s = settings_in(&cat, &root, "custom-races").unwrap();
|
||||
assert_eq!(s.iter().find(|s| s.key == "CoACustomRaces.BotsEnable").unwrap().value, "1");
|
||||
|
||||
// 3. Disable bots for custom races: should write recommended exclusions to playerbots.conf
|
||||
let change_off = BTreeMap::from([("CoACustomRaces.BotsEnable".into(), "0".into())]);
|
||||
assert_eq!(save_settings_in(&cat, &root, &meta, "custom-races", &change_off).unwrap(), ["CoACustomRaces.BotsEnable"]);
|
||||
let pb = fs::read_to_string(dir(&root).join("playerbots.conf")).unwrap();
|
||||
assert!(pb.contains(crate::custom_races::EXCLUDED_BOT_RACES_RECOMMENDED));
|
||||
|
||||
// Settings view now sees "0"
|
||||
let s = settings_in(&cat, &root, "custom-races").unwrap();
|
||||
assert_eq!(s.iter().find(|s| s.key == "CoACustomRaces.BotsEnable").unwrap().value, "0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,14 @@ for (let n = 1; n <= 3; n++) add(`EC.P${n}`, "string", [`NPC phrase ${n}`, `Ре
|
||||
|
||||
add("CoACustomRaces.BotsEnable", "bool",
|
||||
["Bots can be custom races", "Боты кастомных рас", "Bots benutzerdefinierter Völker", "Bots de races personnalisées", "Bots de razas personalizadas", "允许机器人为自定义种族"],
|
||||
["Allow Playerbots to spawn as custom races.", "Разрешает ботам-игрокам появляться кастомными расами.", "Erlaubt Spieler-Bots, benutzerdefinierte Völker zu nutzen.", "Autorise les bots joueurs à s’incarner avec des races personnalisées.", "Permite a los bots jugadores crearse con razas personalizadas.", "允许玩家机器人扮演自定义种族。"]
|
||||
[
|
||||
"Allow Playerbots to spawn as custom races (experimental). Off excludes all custom races; on allows stable custom races.",
|
||||
"Разрешает ботам-игрокам появляться кастомными расами (экспериментально). Выключено — исключает все кастомные расы, включено — разрешает стабильные.",
|
||||
"Erlaubt Spieler-Bots, benutzerdefinierte Völker zu nutzen (experimentell). Aus schließt alle benutzerdefinierten Völker aus; An erlaubt stabile.",
|
||||
"Autorise les bots joueurs à s’incarner avec des races personnalisées (expérimental). Désactivé exclut toutes les races personnalisées ; activé autorise les stables.",
|
||||
"Permite a los bots jugadores crearse con razas personalizadas (experimental). Desactivado excluye todas las razas personalizadas; activado permite las estables.",
|
||||
"允许玩家机器人扮演自定义种族(实验性)。关闭将排除所有自定义种族,开启允许稳定的自定义种族。"
|
||||
]
|
||||
);
|
||||
|
||||
const ah = "AuctionHouseBot.";
|
||||
|
||||
Reference in new issue
Block a user