1use configs::game_config::GameConfig;
24use essences::combat_origin::CombatEventOrigin;
25use essences::entity::Entity;
26use essences::fight_breakdown::CombatSource;
27use essences::fighting::ActiveFight;
28use event_system::script::random::GameRng;
29use uuid::Uuid;
30
31use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
32use crate::event::OverlordEvent;
33use crate::mechanics::balance;
34use crate::mechanics::content::{AbilityInfo, ability_info};
35use crate::mechanics::content_lookups::ContentLookups;
36use crate::mechanics::fight::{AttackParams, NativeSink, heal_entity, stat_throw};
37use crate::mechanics::support_shapes::{SupportAttackCtx, attack_with_supports};
38use essences::ability_stones::AbilityStoneMods;
39
40use super::cast_ability::band_targets;
41
42pub struct CastProjectileCtx<'a> {
44 pub caster_entity: &'a Entity,
46 pub target_entity: &'a Entity,
48 pub fight: &'a ActiveFight,
50 pub rng: &'a GameRng,
52 pub projectile_level: i64,
54 pub config: &'a GameConfig,
55 pub lookups: &'a ContentLookups,
56 pub stones: crate::mechanics::ability_stones::AbilityStoneResolver<'a>,
59 pub source: CombatSource,
63}
64
65pub type CastProjectileFn = fn(&CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>>;
67
68fn info(
72 ctx: &CastProjectileCtx,
73 ability_id: &str,
74) -> anyhow::Result<(AbilityInfo, AbilityStoneMods)> {
75 let id = Uuid::parse_str(ability_id).expect("valid ability uuid literal");
76 let mut info = ability_info(ctx.config, ctx.lookups, id, ctx.projectile_level)?;
77 let mods = ctx.stones.mods_for(ctx.config, id, ctx.projectile_level);
78 info.apply_stone_mods(&mods);
79 Ok((info, mods))
80}
81
82fn run_attack_on(
86 ctx: &CastProjectileCtx,
87 sink: &mut NativeSink,
88 mods: &AbilityStoneMods,
89 target: &Entity,
90 params: &AttackParams,
91) -> anyhow::Result<Option<i64>> {
92 attack_with_supports(
93 sink,
94 &SupportAttackCtx {
95 rng: ctx.rng,
96 lookups: ctx.lookups,
97 fight: ctx.fight,
98 player_id: ctx.fight.player_id,
99 caster: ctx.caster_entity,
100 mods,
101 source: ctx.source,
102 },
103 target,
104 params,
105 )
106}
107
108fn run_attack(
110 ctx: &CastProjectileCtx,
111 sink: &mut NativeSink,
112 mods: &AbilityStoneMods,
113 params: &AttackParams,
114) -> anyhow::Result<Option<i64>> {
115 run_attack_on(ctx, sink, mods, ctx.target_entity, params)
116}
117
118fn attack_power(ctx: &CastProjectileCtx, ability_id: &str) -> anyhow::Result<Vec<OverlordEvent>> {
120 let mut sink = NativeSink::default();
121 let (ability_info, mods) = info(ctx, ability_id)?;
122 let params = AttackParams {
123 power: ability_info.damage,
124 ..Default::default()
125 };
126 run_attack(ctx, &mut sink, &mods, ¶ms)?;
127 Ok(sink.events)
128}
129
130pub fn attack_power_dot(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
133 let mut sink = NativeSink::default();
134 let (ability_info, mods) = info(ctx, "01958172-9e65-7061-9d15-56b2c33cc13e")?;
135 let params = AttackParams {
136 power: ability_info.damage,
137 dot_power: ability_info.dot,
138 ..Default::default()
139 };
140 run_attack(ctx, &mut sink, &mods, ¶ms)?;
141 Ok(sink.events)
142}
143
144pub fn arcane_missiles_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
146 attack_power(ctx, "019589e6-f9dd-7b22-8d39-5350e95aaf69")
147}
148
149pub fn mushroom_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
151 attack_power(ctx, "019a0245-ff0c-7964-b345-ded525c71e74")
152}
153
154pub fn chipmunk_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
156 attack_power(ctx, "019a0246-5aaf-7c01-9a1a-3969e04129ec")
157}
158
159pub fn bat_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
161 attack_power(ctx, "019a0246-cf87-73b0-b701-f8788cf9cc08")
162}
163
164pub fn shot_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
166 attack_power(ctx, "019c00a4-38c9-7859-a642-fd82c25ef285")
167}
168
169pub fn attack_vampiric(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
176 let mut sink = NativeSink::default();
177 let (ability_info, mods) = info(ctx, "019589f7-f4a3-701c-bc0f-f60d980ae250")?;
178 let params = AttackParams {
179 power: ability_info.damage,
180 ..Default::default()
181 };
182 let damage = run_attack(ctx, &mut sink, &mods, ¶ms)?;
183 let Some(damage) = damage else {
184 anyhow::bail!("attack returned no damage; the `damage > 0` contract failed");
185 };
186 if damage > 0 {
187 let vampiric = ability_info.vampiric.unwrap_or(0.0);
188 heal_entity(
189 &mut sink,
190 Some(ctx.caster_entity.id),
191 ctx.caster_entity,
192 damage as f64 * vampiric,
193 ctx.source,
194 )
195 .map_err(|e| anyhow::anyhow!("heal_entity: {e}"))?;
196 }
197 Ok(sink.events)
198}
199
200pub fn counterattack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
203 let mut sink = NativeSink::default();
204 let params = AttackParams {
205 power: Some(balance::COUNTERATTACK_POWER),
206 no_counterattack: true,
207 ..Default::default()
208 };
209 run_attack(ctx, &mut sink, &AbilityStoneMods::identity(), ¶ms)?;
212 Ok(sink.events)
213}
214
215pub fn pet_night_pounce_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
220 let mut sink = NativeSink::default();
221 let (ability_info, mods) = info(ctx, "019584aa-5bde-7ac2-8850-076dafdc4603")?;
222 let params = AttackParams {
223 power: ability_info.damage,
224 crit_chance_bonus: ability_info.crit_chance_bonus.unwrap_or(0.0) * 10000.0,
225 ..Default::default()
226 };
227 run_attack(ctx, &mut sink, &mods, ¶ms)?;
228 Ok(sink.events)
229}
230
231pub fn pet_rust_wave_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
236 let mut sink = NativeSink::default();
237 let (ability_info, mods) = info(ctx, "019584f4-2c99-72bf-bcd3-bfc02fb33977")?;
238 let is_crit = stat_throw(ctx.rng, ctx.lookups, ctx.caster_entity, "crit_chance", 0.0);
239 let targets: Vec<uuid::Uuid> =
240 band_targets(ctx.fight, ctx.caster_entity, ability_info.max_targets)
241 .iter()
242 .map(|e| e.id)
243 .collect();
244 for target_id in targets {
245 let Some(target) = ctx.fight.entities.iter().find(|e| e.id == target_id) else {
246 continue;
247 };
248 let params = AttackParams {
249 power: ability_info.damage,
250 is_crit: Some(is_crit),
251 ..Default::default()
252 };
253 run_attack_on(ctx, &mut sink, &mods, target, ¶ms)?;
254 }
255 Ok(sink.events)
256}
257
258pub fn pet_odd_flame_attack(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
262 let mut sink = NativeSink::default();
263 let (ability_info, mods) = info(ctx, "01958efd-77f9-7dec-8444-c9d759549225")?;
264 let params = AttackParams {
265 dot_power: ability_info.damage,
266 ..Default::default()
267 };
268 run_attack(ctx, &mut sink, &mods, ¶ms)?;
269 Ok(sink.events)
270}
271
272pub fn damage_target_5(ctx: &CastProjectileCtx) -> anyhow::Result<Vec<OverlordEvent>> {
276 Ok(vec![OverlordEvent::Damage {
277 by_entity_id: Some(ctx.caster_entity.id),
278 entity_id: ctx.target_entity.id,
279 damage: 5,
280 damage_data: crate::event::CustomEventData::default(),
281 origin: CombatEventOrigin::Core,
282 source: ctx.source,
283 }])
284}
285
286pub fn register(registry: &mut BehaviorRegistry) {
288 let mut reg = |name: &str, title: &str, desc: &str, f: CastProjectileFn| {
289 registry.register_cast_projectile(
290 BehaviorMeta {
291 name: name.to_string(),
292 category: BehaviorKind::CastProjectile,
293 title: title.to_string(),
294 description: desc.to_string(),
295 },
296 f,
297 );
298 };
299
300 reg(
301 "projectile_damage_target_5",
302 "Снаряд: урон 5 по цели (тест)",
303 "Damage(TargetEntity, 5) — порт test projectile script.",
304 damage_target_5,
305 );
306 reg(
307 "projectile_attack_power_dot",
308 "Снаряд: attack{power,dot} (01966cbc)",
309 "attack(#{power: ability_info.damage, dot_power: ability_info.dot}) \
310 info из 01958172 (порт projectile script 01966cbc).",
311 attack_power_dot,
312 );
313 reg(
314 "projectile_arcane_missiles_attack",
315 "Снаряд: attack{power} (0196a6b3)",
316 "attack(#{power: ability_info.damage}) info из 019589e6 (порт projectile script 0196a6b3).",
317 arcane_missiles_attack,
318 );
319 reg(
320 "projectile_mushroom_attack",
321 "Снаряд: attack{power} (019a0244-4675)",
322 "attack(#{power: ability_info.damage}) info из 019a0245 (порт projectile script 019a0244-4675).",
323 mushroom_attack,
324 );
325 reg(
326 "projectile_chipmunk_attack",
327 "Снаряд: attack{power} (019a0244-7a0a)",
328 "attack(#{power: ability_info.damage}) info из 019a0246-5aaf (порт projectile script 019a0244-7a0a).",
329 chipmunk_attack,
330 );
331 reg(
332 "projectile_bat_attack",
333 "Снаряд: attack{power} (019a0244-aad4)",
334 "attack(#{power: ability_info.damage}) info из 019a0246-cf87 (порт projectile script 019a0244-aad4).",
335 bat_attack,
336 );
337 reg(
338 "projectile_shot_attack",
339 "Снаряд: attack{power} (019c009c)",
340 "attack(#{power: ability_info.damage}) info из 019c00a4 (порт projectile script 019c009c).",
341 shot_attack,
342 );
343 reg(
344 "projectile_attack_vampiric",
345 "Снаряд: вампиризм (0196a6b4)",
346 "attack(#{power}); при уроне>0 heal_entity(Caster, damage*vampiric) \
347 info из 019589f7 (порт projectile script 0196a6b4).",
348 attack_vampiric,
349 );
350 reg(
351 "projectile_pet_night_pounce",
352 "Снаряд: удар с бонусом крита (79f90288, ульта питомца Nyx)",
353 "attack(#{power, crit_chance_bonus}) info из 019584aa — та же математика, \
354 что у ability_crit_strike, но доставка снарядом.",
355 pet_night_pounce_attack,
356 );
357 reg(
358 "projectile_pet_rust_wave",
359 "Снаряд: конус по фронтальным врагам (1ce4597d, ульта питомца Rusty)",
360 "Один общий crit-бросок, attack по врагам в 3-клеточной зоне по x при \
361 прилёте снаряда; info из 019584f4 — та же математика, что у ability_cone_strike.",
362 pet_rust_wave_attack,
363 );
364 reg(
365 "projectile_pet_odd_flame",
366 "Снаряд: чистый DoT-удар (432f231c, ульта питомца Thingy)",
367 "attack(#{dot_power}) info из 01958efd — та же математика, что у \
368 ability_dot_strike, но доставка снарядом.",
369 pet_odd_flame_attack,
370 );
371 reg(
372 "projectile_counterattack",
373 "Снаряд: контратака (019aeeed)",
374 "attack(#{power: COUNTERATTACK_POWER, no_counterattack: true}) \
375 (порт projectile script 019aeeed).",
376 counterattack,
377 );
378}