1use configs::game_config::GameConfig;
12use essences::character_state::CharacterState;
13use essences::fighting::ActiveFight;
14use essences::quest::{QuestGroupType, QuestInstance};
15use uuid::Uuid;
16
17use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
18use crate::event::OverlordEvent;
19use crate::game_config_helpers::GameConfigLookup;
20use crate::mechanics::content_lookups::ContentLookups;
21
22pub struct ConditionalProgressCtx<'a> {
24 pub event: &'a OverlordEvent,
26 pub character_state: &'a CharacterState,
27 pub active_fight: &'a Option<ActiveFight>,
29 pub quest: &'a QuestInstance,
31 pub config: &'a GameConfig,
32 pub lookups: &'a ContentLookups,
33}
34
35pub type ConditionalProgressFn = fn(&ConditionalProgressCtx) -> anyhow::Result<i64>;
39
40pub const DUNGEON_1: u128 = 0x019a9206_800b_781e_8998_38cdc6e9826e;
43pub const DUNGEON_2: u128 = 0x019aee96_7303_7d3e_a382_d7776687d24f;
44pub const DUNGEON_3: u128 = 0x019d2eca_9508_71c9_abb3_a6fc17474502;
45pub const COLLECT_CURRENCY: u128 = 0x0194d64e_2162_76d3_8449_3e850f6e39e9;
46pub const RARITY_A: u128 = 0x0194d64e_2179_797b_90fe_8b783f349203;
47pub const RARITY_B: u128 = 0x0194d64e_2179_797b_90fe_8b799ae7a867;
48pub const RARITY_C: u128 = 0x0194d64e_2179_797b_90fe_8b7ad369fd71;
49pub const LOG_IN_QUEST: u128 = 0x019c2b16_a454_737b_b5b5_1123073e2fce;
50
51const COMPLETE_ALL_LOOP_TASKS: &str = "CompleteAllLoopTasks";
52
53fn is_custom_event(event: &OverlordEvent, ev: &str) -> bool {
55 matches!(event, OverlordEvent::CustomEvent { event_type, .. } if event_type == ev)
56}
57
58fn is_custom_event_any(event: &OverlordEvent) -> bool {
60 matches!(event, OverlordEvent::CustomEvent { .. })
61}
62
63pub fn increment_one(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
67 Ok(ctx.quest.current + 1)
68}
69
70pub fn increment_by_batch_size(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
74 let batch = event_batch_size(ctx.event)?;
75 Ok(ctx.quest.current + batch)
76}
77
78fn event_batch_size(event: &OverlordEvent) -> anyhow::Result<i64> {
81 match event {
82 OverlordEvent::OpenItemCase { batch_size } => Ok(*batch_size),
83 OverlordEvent::AutoChestOpenItemCase { batch_size } => Ok(*batch_size),
84 OverlordEvent::UpdateAutoChestBatchSize { batch_size } => Ok(*batch_size),
85 OverlordEvent::AbilityCaseOpened { batch_size } => Ok(*batch_size as i64),
86 OverlordEvent::PetCaseOpened { batch_size } => Ok(*batch_size as i64),
87 other => Err(anyhow::anyhow!("event {other:?} has no batch_size")),
88 }
89}
90
91pub fn loop_task_increment_one<const N: i64>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
96 if is_custom_event_any(ctx.event) {
97 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
98 return Ok(N);
99 }
100 return Ok(ctx.quest.current);
101 }
102 Ok(ctx.quest.current + 1)
103}
104
105pub fn loop_task_increment_batch<const N: i64>(
107 ctx: &ConditionalProgressCtx,
108) -> anyhow::Result<i64> {
109 if is_custom_event_any(ctx.event) {
110 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
111 return Ok(N);
112 }
113 return Ok(ctx.quest.current);
114 }
115 let batch = event_batch_size(ctx.event)?;
116 Ok(ctx.quest.current + batch)
117}
118
119fn event_level(event: &OverlordEvent) -> anyhow::Result<i64> {
122 match event {
123 OverlordEvent::NewCharacterLevel { level } => Ok(*level),
124 other => Err(anyhow::anyhow!("event {other:?} has no level")),
125 }
126}
127
128pub fn reach_level<const T: i64>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
130 let level = event_level(ctx.event)?;
131 Ok(if level >= T { 1 } else { 0 })
132}
133
134pub fn loop_task_reach_level<const T: i64>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
137 if is_custom_event_any(ctx.event) {
138 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
139 return Ok(1);
140 }
141 return Ok(ctx.quest.current);
142 }
143 let level = event_level(ctx.event)?;
144 Ok(if level >= T { 1 } else { 0 })
145}
146
147pub fn reach_chapter_level<const T: i64>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
151 let chapter = ctx.character_state.character.current_chapter_level;
152 Ok(if chapter >= T { 1 } else { 0 })
153}
154
155pub fn current_chapter_level(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
164 Ok(ctx.character_state.character.current_chapter_level)
165}
166
167pub fn loop_task_reach_chapter_level<const T: i64>(
169 ctx: &ConditionalProgressCtx,
170) -> anyhow::Result<i64> {
171 if is_custom_event_any(ctx.event) {
172 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
173 return Ok(1);
174 }
175 return Ok(ctx.quest.current);
176 }
177 let chapter = ctx.character_state.character.current_chapter_level;
178 Ok(if chapter >= T { 1 } else { 0 })
179}
180
181fn pvp_win_increment(is_win: bool, is_pvp: bool, current: i64) -> i64 {
189 if is_win && is_pvp {
190 current + 1
191 } else {
192 current
193 }
194}
195
196fn pvp_win_signal(event: &OverlordEvent) -> (bool, bool) {
199 match event {
200 OverlordEvent::EndFight {
201 is_win, pvp_state, ..
202 } => (*is_win, pvp_state.is_some()),
203 _ => (false, false),
204 }
205}
206
207pub fn pvp_win(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
211 let (is_win, is_pvp) = pvp_win_signal(ctx.event);
212 Ok(pvp_win_increment(is_win, is_pvp, ctx.quest.current))
213}
214
215pub fn loop_task_pvp_win(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
218 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
219 return Ok(1);
220 }
221 let (is_win, is_pvp) = pvp_win_signal(ctx.event);
222 Ok(pvp_win_increment(is_win, is_pvp, ctx.quest.current))
223}
224
225pub fn raid_dungeon<const D: u128>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
230 let dungeon = Uuid::from_u128(D);
231 if let OverlordEvent::RaidDungeon { dungeon_id, .. } = ctx.event {
232 if *dungeon_id == dungeon {
233 return Ok(ctx.quest.current + 1);
234 }
235 return Ok(ctx.quest.current);
236 }
237 let Some(fight) = ctx.active_fight else {
238 return Ok(ctx.quest.current);
239 };
240 if fight.dungeon.as_ref().map(|d| d.id) == Some(dungeon) {
241 return Ok(ctx.quest.current + 1);
242 }
243 Ok(ctx.quest.current)
244}
245
246pub fn loop_task_raid_dungeon<const D: u128>(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
248 if is_custom_event_any(ctx.event) {
249 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
250 return Ok(1);
251 }
252 return Ok(ctx.quest.current);
253 }
254 raid_dungeon::<D>(ctx)
255}
256
257fn event_quest_id(event: &OverlordEvent) -> anyhow::Result<Uuid> {
261 match event {
262 OverlordEvent::ClaimQuest { quest_id, .. }
263 | OverlordEvent::PatronQuestCompleted { quest_id, .. }
264 | OverlordEvent::HiddenQuestCompleted { quest_id, .. }
265 | OverlordEvent::QuestCompleted { quest_id, .. }
266 | OverlordEvent::UpdateActiveLoopTaskId { quest_id, .. } => Ok(*quest_id),
267 other => Err(anyhow::anyhow!("event {other:?} has no quest_id")),
268 }
269}
270
271fn complete_quest_of_group(
273 ctx: &ConditionalProgressCtx,
274 group: QuestGroupType,
275) -> anyhow::Result<i64> {
276 let quest_id = event_quest_id(ctx.event)?;
277 let tpl = ctx
278 .config
279 .quest(quest_id)
280 .ok_or_else(|| anyhow::anyhow!("quest {quest_id} not found"))?;
281 if tpl.quest_group_type == group {
282 return Ok(ctx.quest.current + 1);
283 }
284 Ok(ctx.quest.current)
285}
286
287pub fn complete_daily_quest(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
288 complete_quest_of_group(ctx, QuestGroupType::Daily)
289}
290pub fn complete_weekly_quest(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
291 complete_quest_of_group(ctx, QuestGroupType::Weekly)
292}
293pub fn complete_loop_task_quest(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
294 complete_quest_of_group(ctx, QuestGroupType::LoopTask)
295}
296
297pub fn loop_task_collect_currency(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
304 if is_custom_event_any(ctx.event) {
305 if is_custom_event(ctx.event, COMPLETE_ALL_LOOP_TASKS) {
306 return Ok(100);
307 }
308 return Ok(ctx.quest.current);
309 }
310 let target = Uuid::from_u128(COLLECT_CURRENCY);
311 let currencies: &[essences::currency::CurrencyUnit] = match ctx.event {
312 OverlordEvent::CurrencyIncrease { currencies, .. } => currencies,
313 OverlordEvent::CurrencyDecrease { currencies, .. } => currencies,
314 other => return Err(anyhow::anyhow!("event {other:?} has no currencies")),
315 };
316 for unit in currencies {
317 if unit.currency_id == target {
318 return Ok(ctx.quest.current + unit.amount);
319 }
320 }
321 Ok(0)
322}
323
324pub fn upgraded_abilities_delta(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
328 let OverlordEvent::UpgradedAbilities { upgraded_abilities } = ctx.event else {
329 return Err(anyhow::anyhow!(
330 "event {:?} has no upgraded_abilities",
331 ctx.event
332 ));
333 };
334 let mut result: i64 = 0;
335 for (_ability_id, (current, final_)) in upgraded_abilities.0.iter() {
336 result += final_ - current;
337 }
338 Ok(ctx.quest.current + result)
339}
340
341pub fn count_equipped_at_rarity<const R: u128>(
347 ctx: &ConditionalProgressCtx,
348) -> anyhow::Result<i64> {
349 let target_id = Uuid::from_u128(R);
350 let target_q = ctx
351 .lookups
352 .item_rarity_q
353 .get(&target_id)
354 .copied()
355 .ok_or_else(|| anyhow::anyhow!("no q for rarity {target_id}"))?;
356
357 if !matches!(ctx.event, OverlordEvent::PlayerEquipItem { .. }) {
358 return Ok(ctx.quest.current);
359 }
360
361 let mut res = 0i64;
362 for item in &ctx.character_state.inventory {
363 if !item.is_equipped {
364 continue;
365 }
366 let rarity_q = ctx
367 .lookups
368 .item_rarity_q
369 .get(&item.rarity.id)
370 .copied()
371 .ok_or_else(|| anyhow::anyhow!("no q for item rarity {}", item.rarity.id))?;
372 if rarity_q >= target_q {
373 res += 1;
374 }
375 }
376 Ok(res)
377}
378
379pub fn always_one(_ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
383 Ok(1)
384}
385
386pub fn log_in_today(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
390 let quest_id = event_quest_id(ctx.event)?;
391 if quest_id == Uuid::from_u128(LOG_IN_QUEST) {
392 return Ok(ctx.quest.current + 1);
393 }
394 Ok(ctx.quest.current)
395}
396
397pub fn quest_event_level_1_then_2(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
401 let level = event_level(ctx.event)?;
402 Ok(match level {
403 1 => 1,
404 2 => 2,
405 _ => 0,
406 })
407}
408
409pub fn quest_current_0_then_1_else_2(ctx: &ConditionalProgressCtx) -> anyhow::Result<i64> {
411 Ok(if ctx.quest.current == 0 { 1 } else { 2 })
412}
413
414pub fn register(registry: &mut BehaviorRegistry) {
416 let mut reg = |name: &str, title: &str, desc: &str, f: ConditionalProgressFn| {
417 registry.register_conditional_progress(
418 BehaviorMeta {
419 name: name.to_string(),
420 category: BehaviorKind::ConditionalProgress,
421 title: title.to_string(),
422 description: desc.to_string(),
423 },
424 f,
425 );
426 };
427
428 reg(
429 "increment_one",
430 "Прогресс +1",
431 "Безусловно увеличивает прогресс квеста на 1.",
432 increment_one,
433 );
434 reg(
435 "increment_by_batch_size",
436 "Прогресс += batch_size",
437 "Увеличивает прогресс на Event.batch_size.",
438 increment_by_batch_size,
439 );
440
441 for (name, title, f) in [
442 (
443 "loop_task_increment_one_n1",
444 "Loop-task: +1, цель 1",
445 loop_task_increment_one::<1> as ConditionalProgressFn,
446 ),
447 (
448 "loop_task_increment_one_n2",
449 "Loop-task: +1, цель 2",
450 loop_task_increment_one::<2>,
451 ),
452 (
453 "loop_task_increment_one_n3",
454 "Loop-task: +1, цель 3",
455 loop_task_increment_one::<3>,
456 ),
457 (
458 "loop_task_increment_one_n5",
459 "Loop-task: +1, цель 5",
460 loop_task_increment_one::<5>,
461 ),
462 (
463 "loop_task_increment_one_n6",
464 "Loop-task: +1, цель 6",
465 loop_task_increment_one::<6>,
466 ),
467 (
468 "loop_task_increment_one_n10",
469 "Loop-task: +1, цель 10",
470 loop_task_increment_one::<10>,
471 ),
472 (
473 "loop_task_increment_one_n12",
474 "Loop-task: +1, цель 12",
475 loop_task_increment_one::<12>,
476 ),
477 (
478 "loop_task_increment_one_n20",
479 "Loop-task: +1, цель 20",
480 loop_task_increment_one::<20>,
481 ),
482 ] {
483 reg(
484 name,
485 title,
486 "CompleteAllLoopTasks => цель; иначе прогресс +1.",
487 f,
488 );
489 }
490 reg(
491 "loop_task_increment_batch_n1",
492 "Loop-task: += batch_size, цель 1",
493 "CompleteAllLoopTasks => 1; иначе += Event.batch_size (1 pull completes даже при batch).",
494 loop_task_increment_batch::<1>,
495 );
496 reg(
497 "loop_task_increment_batch_n10",
498 "Loop-task: += batch_size, цель 10",
499 "CompleteAllLoopTasks => 10; иначе += Event.batch_size.",
500 loop_task_increment_batch::<10>,
501 );
502
503 reg(
504 "reach_level_2",
505 "Достичь уровня 2",
506 "Event.level >= 2 => 1, иначе 0 (без loop-task guard).",
507 reach_level::<2>,
508 );
509 for (name, title, f) in [
510 (
511 "loop_task_reach_level_3",
512 "Loop-task: достичь уровня 3",
513 loop_task_reach_level::<3> as ConditionalProgressFn,
514 ),
515 (
516 "loop_task_reach_level_5",
517 "Loop-task: достичь уровня 5",
518 loop_task_reach_level::<5>,
519 ),
520 (
521 "loop_task_reach_level_10",
522 "Loop-task: достичь уровня 10",
523 loop_task_reach_level::<10>,
524 ),
525 (
526 "loop_task_reach_level_15",
527 "Loop-task: достичь уровня 15",
528 loop_task_reach_level::<15>,
529 ),
530 (
531 "loop_task_reach_level_20",
532 "Loop-task: достичь уровня 20",
533 loop_task_reach_level::<20>,
534 ),
535 (
536 "loop_task_reach_level_25",
537 "Loop-task: достичь уровня 25",
538 loop_task_reach_level::<25>,
539 ),
540 (
541 "loop_task_reach_level_26",
542 "Loop-task: достичь уровня 26",
543 loop_task_reach_level::<26>,
544 ),
545 (
546 "loop_task_reach_level_27",
547 "Loop-task: достичь уровня 27",
548 loop_task_reach_level::<27>,
549 ),
550 (
551 "loop_task_reach_level_28",
552 "Loop-task: достичь уровня 28",
553 loop_task_reach_level::<28>,
554 ),
555 (
556 "loop_task_reach_level_29",
557 "Loop-task: достичь уровня 29",
558 loop_task_reach_level::<29>,
559 ),
560 (
561 "loop_task_reach_level_30",
562 "Loop-task: достичь уровня 30",
563 loop_task_reach_level::<30>,
564 ),
565 (
566 "loop_task_reach_level_35",
567 "Loop-task: достичь уровня 35",
568 loop_task_reach_level::<35>,
569 ),
570 (
571 "loop_task_reach_level_40",
572 "Loop-task: достичь уровня 40",
573 loop_task_reach_level::<40>,
574 ),
575 (
576 "loop_task_reach_level_45",
577 "Loop-task: достичь уровня 45",
578 loop_task_reach_level::<45>,
579 ),
580 (
581 "loop_task_reach_level_50",
582 "Loop-task: достичь уровня 50",
583 loop_task_reach_level::<50>,
584 ),
585 (
586 "loop_task_reach_level_60",
587 "Loop-task: достичь уровня 60",
588 loop_task_reach_level::<60>,
589 ),
590 (
591 "loop_task_reach_level_100",
592 "Loop-task: достичь уровня 100",
593 loop_task_reach_level::<100>,
594 ),
595 ] {
596 reg(
597 name,
598 title,
599 "CompleteAllLoopTasks => 1; иначе Event.level >= порог ? 1 : 0.",
600 f,
601 );
602 }
603
604 for (name, title, f) in [
607 (
608 "reach_chapter_level_1",
609 "Достичь главы 1",
610 reach_chapter_level::<1> as ConditionalProgressFn,
611 ),
612 (
613 "reach_chapter_level_2",
614 "Достичь главы 2",
615 reach_chapter_level::<2>,
616 ),
617 (
618 "reach_chapter_level_3",
619 "Достичь главы 3",
620 reach_chapter_level::<3>,
621 ),
622 (
623 "reach_chapter_level_4",
624 "Достичь главы 4",
625 reach_chapter_level::<4>,
626 ),
627 (
628 "reach_chapter_level_5",
629 "Достичь главы 5",
630 reach_chapter_level::<5>,
631 ),
632 (
633 "reach_chapter_level_6",
634 "Достичь главы 6",
635 reach_chapter_level::<6>,
636 ),
637 (
638 "reach_chapter_level_7",
639 "Достичь главы 7",
640 reach_chapter_level::<7>,
641 ),
642 (
643 "reach_chapter_level_8",
644 "Достичь главы 8",
645 reach_chapter_level::<8>,
646 ),
647 (
648 "reach_chapter_level_9",
649 "Достичь главы 9",
650 reach_chapter_level::<9>,
651 ),
652 (
653 "reach_chapter_level_10",
654 "Достичь главы 10",
655 reach_chapter_level::<10>,
656 ),
657 (
658 "reach_chapter_level_11",
659 "Достичь главы 11",
660 reach_chapter_level::<11>,
661 ),
662 (
663 "reach_chapter_level_12",
664 "Достичь главы 12",
665 reach_chapter_level::<12>,
666 ),
667 (
668 "reach_chapter_level_13",
669 "Достичь главы 13",
670 reach_chapter_level::<13>,
671 ),
672 (
673 "reach_chapter_level_14",
674 "Достичь главы 14",
675 reach_chapter_level::<14>,
676 ),
677 (
678 "reach_chapter_level_15",
679 "Достичь главы 15",
680 reach_chapter_level::<15>,
681 ),
682 (
683 "reach_chapter_level_16",
684 "Достичь главы 16",
685 reach_chapter_level::<16>,
686 ),
687 (
688 "reach_chapter_level_17",
689 "Достичь главы 17",
690 reach_chapter_level::<17>,
691 ),
692 (
693 "reach_chapter_level_18",
694 "Достичь главы 18",
695 reach_chapter_level::<18>,
696 ),
697 (
698 "reach_chapter_level_19",
699 "Достичь главы 19",
700 reach_chapter_level::<19>,
701 ),
702 (
703 "reach_chapter_level_20",
704 "Достичь главы 20",
705 reach_chapter_level::<20>,
706 ),
707 ] {
708 reg(
709 name,
710 title,
711 "current_chapter_level >= N => 1, иначе 0 (без guard).",
712 f,
713 );
714 }
715 reg(
718 "reach_chapter_level_28",
719 "Достичь главы 28",
720 "current_chapter_level >= 28 => 1, иначе 0 (без guard).",
721 reach_chapter_level::<28>,
722 );
723 for (name, title, f) in [
724 (
725 "loop_task_reach_chapter_level_5",
726 "Loop-task: глава 5",
727 loop_task_reach_chapter_level::<5> as ConditionalProgressFn,
728 ),
729 (
730 "loop_task_reach_chapter_level_6",
731 "Loop-task: глава 6",
732 loop_task_reach_chapter_level::<6>,
733 ),
734 (
735 "loop_task_reach_chapter_level_7",
736 "Loop-task: глава 7",
737 loop_task_reach_chapter_level::<7>,
738 ),
739 (
740 "loop_task_reach_chapter_level_8",
741 "Loop-task: глава 8",
742 loop_task_reach_chapter_level::<8>,
743 ),
744 (
745 "loop_task_reach_chapter_level_9",
746 "Loop-task: глава 9",
747 loop_task_reach_chapter_level::<9>,
748 ),
749 (
750 "loop_task_reach_chapter_level_10",
751 "Loop-task: глава 10",
752 loop_task_reach_chapter_level::<10>,
753 ),
754 (
755 "loop_task_reach_chapter_level_11",
756 "Loop-task: глава 11",
757 loop_task_reach_chapter_level::<11>,
758 ),
759 (
760 "loop_task_reach_chapter_level_15",
761 "Loop-task: глава 15",
762 loop_task_reach_chapter_level::<15>,
763 ),
764 (
765 "loop_task_reach_chapter_level_20",
766 "Loop-task: глава 20",
767 loop_task_reach_chapter_level::<20>,
768 ),
769 (
770 "loop_task_reach_chapter_level_25",
771 "Loop-task: глава 25",
772 loop_task_reach_chapter_level::<25>,
773 ),
774 (
775 "loop_task_reach_chapter_level_30",
776 "Loop-task: глава 30",
777 loop_task_reach_chapter_level::<30>,
778 ),
779 (
780 "loop_task_reach_chapter_level_35",
781 "Loop-task: глава 35",
782 loop_task_reach_chapter_level::<35>,
783 ),
784 (
785 "loop_task_reach_chapter_level_40",
786 "Loop-task: глава 40",
787 loop_task_reach_chapter_level::<40>,
788 ),
789 ] {
790 reg(
791 name,
792 title,
793 "CompleteAllLoopTasks => 1; иначе current_chapter_level >= порог ? 1 : 0.",
794 f,
795 );
796 }
797
798 reg(
799 "current_chapter_level",
800 "Текущая глава (порог = progress_target)",
801 "Возвращает current_chapter_level; квест завершён при достижении главы progress_target. Используется тирами прогресс-пасса (ch35→130).",
802 current_chapter_level,
803 );
804
805 reg(
806 "pvp_win",
807 "Победа в PvP",
808 "EndFight с pvp_state => +1, иначе без изменений.",
809 pvp_win,
810 );
811 reg(
812 "loop_task_pvp_win",
813 "Loop-task: победа в PvP",
814 "CompleteAllLoopTasks => 1; иначе EndFight с pvp_state => +1.",
815 loop_task_pvp_win,
816 );
817
818 for (name, title, f) in [
819 (
820 "raid_dungeon_1",
821 "Рейд подземелья D1",
822 raid_dungeon::<DUNGEON_1> as ConditionalProgressFn,
823 ),
824 (
825 "raid_dungeon_2",
826 "Рейд подземелья D2",
827 raid_dungeon::<DUNGEON_2>,
828 ),
829 (
830 "raid_dungeon_3",
831 "Рейд подземелья D3",
832 raid_dungeon::<DUNGEON_3>,
833 ),
834 (
835 "loop_task_raid_dungeon_1",
836 "Loop-task: рейд D1",
837 loop_task_raid_dungeon::<DUNGEON_1>,
838 ),
839 (
840 "loop_task_raid_dungeon_2",
841 "Loop-task: рейд D2",
842 loop_task_raid_dungeon::<DUNGEON_2>,
843 ),
844 (
845 "loop_task_raid_dungeon_3",
846 "Loop-task: рейд D3",
847 loop_task_raid_dungeon::<DUNGEON_3>,
848 ),
849 ] {
850 reg(
851 name,
852 title,
853 "RaidDungeon/ActiveFight по целевому dungeon => +1 (loop-task варианты с guard).",
854 f,
855 );
856 }
857
858 reg(
859 "complete_daily_quest",
860 "Завершить дневной квест",
861 "+1 если завершённый квест имеет group_type Daily.",
862 complete_daily_quest,
863 );
864 reg(
865 "complete_weekly_quest",
866 "Завершить недельный квест",
867 "+1 если завершённый квест имеет group_type Weekly.",
868 complete_weekly_quest,
869 );
870 reg(
871 "complete_loop_task_quest",
872 "Завершить loop-task квест",
873 "+1 если завершённый квест имеет group_type LoopTask.",
874 complete_loop_task_quest,
875 );
876
877 reg(
878 "loop_task_collect_currency",
879 "Loop-task: собрать валюту",
880 "CompleteAllLoopTasks => 100; иначе += amount нужной валюты, иначе 0.",
881 loop_task_collect_currency,
882 );
883 reg(
884 "upgraded_abilities_delta",
885 "Сумма апгрейдов способностей",
886 "+= сумма (final - current) по Event.upgraded_abilities.",
887 upgraded_abilities_delta,
888 );
889
890 for (name, title, f) in [
891 (
892 "count_equipped_rarity_a",
893 "Счётчик экип. предметов редкости A",
894 count_equipped_at_rarity::<RARITY_A> as ConditionalProgressFn,
895 ),
896 (
897 "count_equipped_rarity_b",
898 "Счётчик экип. предметов редкости B",
899 count_equipped_at_rarity::<RARITY_B>,
900 ),
901 (
902 "count_equipped_rarity_c",
903 "Счётчик экип. предметов редкости C",
904 count_equipped_at_rarity::<RARITY_C>,
905 ),
906 ] {
907 reg(
908 name,
909 title,
910 "Кол-во экипированных предметов с q >= q целевой редкости (PlayerEquipItem).",
911 f,
912 );
913 }
914
915 reg(
916 "always_one",
917 "Всегда 1",
918 "Безусловно возвращает 1.",
919 always_one,
920 );
921 reg(
922 "log_in_today",
923 "Вход сегодня",
924 "+1 если Event.quest_id совпадает с дневным квестом входа.",
925 log_in_today,
926 );
927
928 reg(
929 "quest_event_level_1_then_2",
930 "Тест: level 1 => 1, level 2 => 2",
931 "Тестовый прогресс: level 1 => 1, level 2 => 2, иначе 0.",
932 quest_event_level_1_then_2,
933 );
934 reg(
935 "quest_current_0_then_1_else_2",
936 "Тест: current 0 => 1, иначе 2",
937 "Тестовый прогресс: current 0 => 1, иначе 2.",
938 quest_current_0_then_1_else_2,
939 );
940}
941
942#[cfg(test)]
943mod tests {
944 use super::*;
945
946 #[test]
951 fn pvp_win_counts_only_player_victories() {
952 assert_eq!(pvp_win_increment(true, true, 5), 6);
954 assert_eq!(pvp_win_increment(false, true, 5), 5);
956 assert_eq!(pvp_win_increment(true, false, 5), 5);
958 assert_eq!(pvp_win_increment(false, false, 5), 5);
960 }
961
962 #[test]
965 fn pvp_win_signal_reads_only_endfight() {
966 let non_fight = OverlordEvent::PetCaseOpened { batch_size: 1 };
967 assert_eq!(pvp_win_signal(&non_fight), (false, false));
968 }
969}