wowlab_engine_application/settings/
mod.rs1use std::{collections::BTreeMap, str::FromStr};
4
5use strum::IntoEnumIterator;
6use wowlab_common::sim::intent::{IntentConfigError, SimConfigIntent, parse_sim_config};
7use wowlab_engine_ports::EngineError;
8use wowlab_types::{
9 constants::RACIAL_ON_USE_SPELL_IDS,
10 game::{RaceId, SpecId},
11};
12
13#[must_use]
15pub(crate) fn racial_spell_id_for_race(race: RaceId) -> Option<u32> {
16 RACIAL_ON_USE_SPELL_IDS
17 .iter()
18 .find(|(r, _)| *r == race)
19 .map(|(_, id)| *id)
20}
21
22pub(crate) fn parse_config(sim_config: &str) -> Result<SimConfigIntent, IntentConfigError> {
23 parse_sim_config(sim_config)
24}
25
26pub(crate) fn parse_spec(config: &SimConfigIntent) -> Result<SpecId, EngineError> {
27 match SpecId::parse_wow_spec_id(config.spec) {
28 Ok(spec) => Ok(spec),
29 Err(error) => Err(EngineError::intent_validation(error.to_string())),
30 }
31}
32
33pub(crate) fn resolve_bloodlust(settings: &BTreeMap<String, toml::Value>) -> bool {
34 settings
35 .get("bloodlust")
36 .and_then(toml::Value::as_bool)
37 .unwrap_or(false)
38}
39
40pub(crate) fn resolve_raid_events(
41 settings: &BTreeMap<String, toml::Value>,
42) -> Result<Vec<wowlab_engine_ports::RaidEventConfig>, EngineError> {
43 settings.get("raid_events").map_or_else(
44 || Ok(Vec::new()),
45 |value| {
46 value.clone().try_into().map_err(|error| {
47 EngineError::intent_validation(format!(
48 "settings[\"raid_events\"] is invalid: {error}"
49 ))
50 })
51 },
52 )
53}
54
55pub(crate) fn resolve_external_buffs(
56 settings: &BTreeMap<String, toml::Value>,
57) -> Result<Vec<wowlab_engine_ports::ExternalBuffConfig>, EngineError> {
58 settings.get("external_buffs").map_or_else(
59 || Ok(Vec::new()),
60 |value| {
61 value.clone().try_into().map_err(|error| {
62 EngineError::intent_validation(format!(
63 "settings[\"external_buffs\"] is invalid: {error}"
64 ))
65 })
66 },
67 )
68}
69
70pub(crate) fn resolve_bug_settings(
72 settings: &BTreeMap<String, toml::Value>,
73) -> wowlab_engine_ports::BugSettings {
74 let default_enabled = settings
75 .get("bugs")
76 .and_then(toml::Value::as_bool)
77 .unwrap_or(true);
78 let mut overrides = Vec::new();
79
80 for (key, enabled) in [("bugs_disable", false), ("bugs_enable", true)] {
81 let Some(slugs) = settings.get(key).and_then(toml::Value::as_array) else {
82 continue;
83 };
84
85 for slug in slugs {
86 if let Some(slug) = slug.as_str() {
87 overrides.push((slug.to_string(), enabled));
89 }
90 }
91 }
92
93 wowlab_engine_ports::BugSettings {
94 default_enabled,
95 overrides,
96 }
97}
98
99pub(crate) fn resolve_cast_latency(
100 settings: &BTreeMap<String, toml::Value>,
101) -> Result<wowlab_engine_ports::CastLatency, EngineError> {
102 fn value(
103 settings: &BTreeMap<String, toml::Value>,
104 key: &str,
105 default: u32,
106 ) -> Result<u32, EngineError> {
107 match settings.get(key) {
108 None => Ok(default),
109 Some(value) => value
110 .as_integer()
111 .and_then(|value| u32::try_from(value).ok())
112 .ok_or_else(|| {
113 EngineError::intent_validation(format!(
114 "settings[\"{key}\"] must be a nonnegative integer"
115 ))
116 }),
117 }
118 }
119
120 let defaults = wowlab_engine_ports::CastLatency::default();
121
122 Ok(wowlab_engine_ports::CastLatency {
123 queue_ms: value(settings, "queue_lag_ms", defaults.queue_ms)?,
124 gcd_ms: value(settings, "gcd_lag_ms", defaults.gcd_ms)?,
125 channel_ms: value(settings, "channel_lag_ms", defaults.channel_ms)?,
126 queue_window_ms: value(settings, "spell_queue_window_ms", defaults.queue_window_ms)?,
127 strict_gcd_queue: settings.get("strict_gcd_queue").map_or(
128 Ok(defaults.strict_gcd_queue),
129 |value| {
130 value.as_bool().ok_or_else(|| {
131 EngineError::intent_validation(
132 "settings[\"strict_gcd_queue\"] must be a boolean",
133 )
134 })
135 },
136 )?,
137 })
138}
139
140fn supported_race_names() -> String {
141 RaceId::iter()
142 .map(|r| r.to_string())
143 .collect::<Vec<_>>()
144 .join(", ")
145}
146
147pub(crate) fn resolve_race_setting(
148 settings: &BTreeMap<String, toml::Value>,
149) -> Result<RaceId, EngineError> {
150 match settings.get("race") {
151 None => Ok(RaceId::Human),
152 Some(v) => match v.as_str() {
153 None => Err(EngineError::intent_validation(format!(
154 "settings[\"race\"] must be a string, got {v:?}; supported: {}",
155 supported_race_names()
156 ))),
157 Some(s) => RaceId::from_str(s).map_err(|e| {
158 EngineError::intent_validation(format!(
159 "unknown race {s:?}: {e}; supported: {}",
160 supported_race_names()
161 ))
162 }),
163 },
164 }
165}
166
167mod consumables;
168
169pub(crate) use consumables::{parse_consumable_selection, resolve_consumable_spells};
170
171#[cfg(test)]
172mod tests;