wowlab_engine_application/
game_data.rs1mod auras;
4mod expected_stats;
5mod passives;
6mod powers;
7mod resolution_ids;
8mod spells;
9
10#[cfg(test)]
11use std::collections::VecDeque;
12
13use auras::resolve_auras;
14use expected_stats::{apply_expected_stats, resolve_expected_stats};
15use passives::{EquippedWeapons, PassiveData, PassiveInputs, resolve_passives};
16use powers::resolve_scaling_and_power_types;
17use resolution_ids::{ResolvedPassiveSeeds, collect_resolution_ids};
18use spells::{SpellResolutionContext, resolve_spells};
19#[cfg(test)]
20use wowlab_engine_domain::dbc::{AuraSubtypeKind, PowerModifiers};
21#[cfg(test)]
22use wowlab_engine_gamedata::PowerTypeProps;
23use wowlab_engine_gamedata::{ConsumableSpells, ResolvedGameData, ResolvedGameDataBuilder};
24#[cfg(test)]
25use wowlab_engine_ports::SpellId;
26use wowlab_engine_ports::{DynDataResolver, EngineError, SpecDescriptor, TalentSelection};
27#[cfg(test)]
28use wowlab_types::data::{ExpectedStatFlat, PowerTypeFlat, SpellDataFlat, SpellEffect};
29use wowlab_types::{
30 data::WeaponStats,
31 game::RaceId,
32 sim::{FastSet, SpellIdx},
33};
34
35use crate::{ApplicationError, ApplicationStage, error::EngineResultExt as _};
36
37#[rustfmt::skip]
38pub(crate) use passives::{resolve_race_passive_spells, resolve_stat_passives};
39
40#[derive(Clone, Debug)]
42pub struct CharacterContext {
43 pub main_hand: WeaponStats,
44 pub race: Option<RaceId>,
46 pub off_hand: Option<WeaponStats>,
47 pub player_expansion_id: u32,
48 pub player_level: u16,
49 pub consumables: ConsumableSpells,
50}
51
52fn initialize_builder(character: CharacterContext) -> ResolvedGameDataBuilder {
53 let mut builder = ResolvedGameData::builder();
54
55 builder.set_main_hand(character.main_hand);
56 builder.set_off_hand(character.off_hand);
57 builder.set_level(u32::from(character.player_level));
58 builder.set_consumable_spells(character.consumables);
59
60 builder
61}
62
63enum ExpectedStatsInput {
64 Resolve,
65 PreResolved {
66 creature_armor: f64,
67 armor_constant: f64,
68 },
69}
70
71#[derive(Debug)]
72pub struct GameDataInputs<'a> {
73 pub descriptor: &'a SpecDescriptor,
74 pub extra_spell_ids: &'a [u32],
75 pub talents: &'a [TalentSelection],
76 pub set_bonus_auras: &'a [u32],
77 pub character: CharacterContext,
78}
79
80pub(crate) struct PreResolvedEnemyStats {
81 pub creature_armor: f64,
82 pub armor_constant: f64,
83}
84
85pub async fn resolve_game_data(
87 resolver: &DynDataResolver<'_>,
88 inputs: GameDataInputs<'_>,
89) -> Result<ResolvedGameData, ApplicationError> {
90 resolve_game_data_inner(resolver, inputs, ExpectedStatsInput::Resolve)
91 .await
92 .in_application_stage(ApplicationStage::GameDataResolution)
93}
94
95pub(crate) async fn resolve_game_data_with_expected_stats(
96 resolver: &DynDataResolver<'_>,
97 inputs: GameDataInputs<'_>,
98 resolved: PreResolvedEnemyStats,
99) -> Result<ResolvedGameData, EngineError> {
100 resolve_game_data_inner(
101 resolver,
102 inputs,
103 ExpectedStatsInput::PreResolved {
104 creature_armor: resolved.creature_armor,
105 armor_constant: resolved.armor_constant,
106 },
107 )
108 .await
109}
110
111async fn resolve_game_data_inner(
112 resolver: &DynDataResolver<'_>,
113 inputs: GameDataInputs<'_>,
114 expected_stats: ExpectedStatsInput,
115) -> Result<ResolvedGameData, EngineError> {
116 let GameDataInputs {
117 descriptor,
118 extra_spell_ids,
119 talents,
120 set_bonus_auras,
121 character,
122 } = inputs;
123 let mut builder = initialize_builder(character.clone());
124 let power_modifiers = resolve_scaling_and_power_types(&mut builder, resolver).await?;
125 let PassiveData {
126 passives,
127 effect_overrides,
128 crit_chance_scaled,
129 active_aura_spell_ids,
130 overrides,
131 resolvable_spec_spells,
132 class_target_debuffs,
133 } = resolve_passives(
134 &mut builder,
135 resolver,
136 PassiveInputs {
137 descriptor,
138 talents,
139 set_bonus_auras,
140 weapons: EquippedWeapons {
141 main_hand: &character.main_hand,
142 off_hand: character.off_hand.as_ref(),
143 },
144 race: character.race,
145 },
146 &power_modifiers,
147 )
148 .await?;
149
150 let metadata = &descriptor.metadata;
151
152 metadata.validate()?;
153 let (aura_ids, required_spell_ids, mut pending) = collect_resolution_ids(
154 metadata.spell_ids,
155 metadata.aura_ids,
156 extra_spell_ids,
157 talents,
158 &ResolvedPassiveSeeds {
159 overrides: &overrides,
160 spec_spells: &resolvable_spec_spells,
161 class_target_debuffs: &class_target_debuffs,
162 },
163 );
164 let mut resolved = FastSet::default();
165
166 resolve_spells(&mut SpellResolutionContext {
167 builder: &mut builder,
168 resolver,
169 talents,
170 passives: &passives,
171 crit_chance_scaled: &crit_chance_scaled,
172 effect_overrides: &effect_overrides,
173 power_modifiers: &power_modifiers,
174 active_aura_spell_ids: &active_aura_spell_ids,
175 primary_resource_type: i32::from(u8::from(metadata.primary_resource)),
176 secondary_resource_type: metadata
177 .secondary_resource
178 .map(|resource| i32::from(u8::from(resource))),
179 required_spell_ids: &required_spell_ids,
180 pending: &mut pending,
181 resolved: &mut resolved,
182 })
183 .await?;
184
185 for entry in metadata.server_proc_entries {
186 let inserted = builder
187 .insert_server_proc_attributes(SpellIdx::from_raw(entry.spell_id), entry.attributes);
188
189 debug_assert!(
190 inserted,
191 "validated server proc entries reference resolved manifest spells"
192 );
193 tokio::task::yield_now().await;
194 }
195
196 resolve_auras(&mut builder, resolver, &aura_ids, &passives).await?;
197
198 match expected_stats {
199 ExpectedStatsInput::Resolve => {
200 resolve_expected_stats(&mut builder, resolver, &character, &passives).await?;
201 }
202 ExpectedStatsInput::PreResolved {
203 creature_armor,
204 armor_constant,
205 } => apply_expected_stats(&mut builder, creature_armor, armor_constant, &passives),
206 }
207
208 Ok(builder.build())
209}
210
211#[cfg(test)]
212use passives::{
213 apply_declared_passive_effect_overrides, apply_effect_precision_scales, apply_spell_overrides,
214 apply_talent_effect_overrides, mask_spec_conditional_passive_effects,
215 retain_statically_folded_passives,
216};
217#[cfg(test)]
218use powers::{apply_resource_passives, normalized_power_type_props};
219#[cfg(test)]
220use spells::{
221 apply_runtime_talent_effect, insert_effect, insert_spell_properties, resolve_effect_payload,
222 resolve_trigger_chain_payload,
223};
224
225#[cfg(test)]
226#[path = "game_data_tests.rs"]
227mod game_data_tests;