aboutsummaryrefslogtreecommitdiff
path: root/src/analyzers/helpers.rs
blob: 067f57572ddbb086259fdba982f71da769d4e905 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
//! This module contains helper methods that are used in different analyzers.
use std::collections::HashMap;

use crate::{AgentKind, EventKind, Log};

/// Check if the log was rewarded, and if yes, return `Outcome::Success` early.
macro_rules! check_reward {
    ($log:expr) => {
        let log: &Log = $log;
        if log.was_rewarded() {
            return Some(crate::analyzers::Outcome::Success);
        }
    };
}

/// Returns the maximum health of the boss agent.
///
/// If the health cannot be determined, this function returns `None`.
///
/// The boss agent is determined by using [`Log::is_boss`][Log::is_boss].
pub fn boss_health(log: &Log) -> Option<u64> {
    let mut health: Option<u64> = None;
    for event in log.events() {
        if let EventKind::MaxHealthUpdate {
            agent_addr,
            max_health,
        } = *event.kind()
        {
            if log.is_boss(agent_addr) {
                health = health.map(|h| h.max(max_health)).or(Some(max_health));
            }
        }
    }
    health
}

/// Checks if any of the boss NPCs have died.
///
/// Death is determined by checking for the [`EventKind::ChangeDead`][EventKind::ChangeDead] event,
/// and whether a NPC is a boss is determined by the [`Log::is_boss`][Log::is_boss] method.
pub fn boss_is_dead(log: &Log) -> bool {
    log.events().iter().any(
        |ev| matches!(ev.kind(), EventKind::ChangeDead { agent_addr } if log.is_boss(*agent_addr)),
    )
}

/// Checks whether the players exit combat after the boss.
///
/// This is useful to determine the success state of some fights.
pub fn players_exit_after_boss(log: &Log) -> bool {
    let mut player_exit = 0u64;
    let mut boss_exit = 0u64;

    for event in log.events() {
        if let EventKind::ExitCombat { agent_addr } = event.kind() {
            let agent = if let Some(a) = log.agent_by_addr(*agent_addr) {
                a
            } else {
                continue;
            };

            match agent.kind() {
                AgentKind::Player(_) if event.time() >= player_exit => {
                    player_exit = event.time();
                }
                AgentKind::Character(_)
                    if event.time() >= boss_exit && log.is_boss(*agent_addr) =>
                {
                    boss_exit = event.time();
                }
                _ => (),
            }
        }
    }
    // Safety margin
    boss_exit != 0 && player_exit > boss_exit + 1000
}

/// Checks if the given buff is present in the log.
pub fn buff_present(log: &Log, wanted_buff_id: u32) -> bool {
    for event in log.events() {
        if let EventKind::BuffApplication { buff_id, .. } = *event.kind() {
            if buff_id == wanted_buff_id {
                return true;
            }
        }
    }
    false
}

/// Returns the (minimum) time between applications of the given buff in milliseconds.
pub fn time_between_buffs(log: &Log, wanted_buff_id: u32) -> u64 {
    let mut time_maps: HashMap<u64, Vec<u64>> = HashMap::new();
    for event in log.events() {
        if let EventKind::BuffApplication {
            destination_agent_addr,
            buff_id,
            ..
        } = event.kind()
        {
            if *buff_id == wanted_buff_id {
                time_maps
                    .entry(*destination_agent_addr)
                    .or_default()
                    .push(event.time());
            }
        }
    }
    let timestamps = if let Some(ts) = time_maps.values().max_by_key(|v| v.len()) {
        ts
    } else {
        return 0;
    };
    timestamps
        .iter()
        .zip(timestamps.iter().skip(1))
        .map(|(a, b)| b - a)
        // Arbitrary limit to filter out duplicated buff application events
        .filter(|x| *x > 50)
        .min()
        .unwrap_or(0)
}