File size: 5,639 Bytes
b76daae
 
 
 
2452398
6ab7697
6e6388e
5825182
2452398
 
b76daae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5825182
 
 
2452398
b76daae
 
2452398
5825182
 
 
b76daae
5825182
 
 
 
 
 
6e6388e
5825182
6e6388e
5825182
6e6388e
2452398
5825182
 
 
 
 
6e6388e
5825182
6e6388e
5825182
6e6388e
2452398
 
b76daae
 
 
 
 
 
 
 
 
3ed500d
b76daae
2452398
b76daae
6ab7697
2452398
 
5825182
 
b76daae
 
55f0dce
 
b76daae
 
 
 
 
5825182
b76daae
3ed500d
b76daae
5825182
 
 
b76daae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e6388e
 
60d173b
 
 
6e6388e
 
 
 
6d09f00
 
 
6e6388e
6d09f00
b76daae
60d173b
b76daae
2452398
 
 
 
 
 
 
b76daae
2452398
 
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from dataclasses import dataclass
from collections import defaultdict
from typing import Dict

from base.attribute import Attribute
from base.constant import FRAME_PER_SECOND
from base.skill import Skill, DotDamage
from utils.parser import School


@dataclass
class Detail:
    damage: int = 0
    critical_damage: int = 0
    expected_damage: float = 0.
    critical_strike: float = 0.

    gradients: Dict[str, float] = None

    critical_count: int = 0
    count: int = 0

    def __post_init__(self):
        self.gradients = defaultdict(float)

    @property
    def actual_critical_strike(self):
        if self.count:
            return self.critical_count / self.count
        return 0


def filter_status(status, school: School, skill_id):
    buffs = []
    for buff_id, buff_level, buff_stack in status:
        buff = school.buffs[buff_id]
        if not buff.activate:
            continue
        buff.buff_level, buff.buff_stack = buff_level, buff_stack
        if buff.gain_attributes or skill_id in buff.gain_skills:
            buffs.append(buff)

    return tuple(sorted(buffs, key=lambda x: x.buff_id))


def add_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
    if not snapshot_buffs:
        for buff in current_buffs:
            buff.add_all(attribute, skill)
    elif isinstance(skill, DotDamage):
        for buff in snapshot_buffs:
            buff.add_dot(attribute, skill, True)
        for buff in current_buffs:
            buff.add_dot(attribute, skill, False)


def sub_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
    if not snapshot_buffs:
        for buff in current_buffs:
            buff.sub_all(attribute, skill)
    elif isinstance(skill, DotDamage):
        for buff in snapshot_buffs:
            buff.sub_dot(attribute, skill, True)
        for buff in current_buffs:
            buff.sub_dot(attribute, skill, False)


def concat_buffs(current_buffs, snapshot_buffs):
    buffs = ",".join(buff.display_name for buff in current_buffs)
    if snapshot_buffs and current_buffs != snapshot_buffs:
        buffs += f"({','.join(buff.display_name for buff in snapshot_buffs)})"
    if not buffs:
        buffs = "~"
    return buffs


def analyze_details(record, duration: int, attribute: Attribute, school: School):
    total = Detail()
    details = {}
    summary = {}
    duration *= FRAME_PER_SECOND

    for skill, status in record.items():
        skill_id, skill_level, skill_stack = skill
        skill: Skill = school.skills[skill_id]
        if not skill.activate:
            continue
        skill.skill_level, skill.skill_stack = skill_level, skill_stack
        skill_name = skill.skill_name

        skill_detail = details[skill.display_name] = {}
        if not (skill_summary := summary.get(skill_name)):
            skill_summary = summary[skill_name] = Detail()
        skill_total = skill_detail[""] = Detail()
        for (current_status, snapshot_status), timeline in status.items():
            if not (timeline := [t for t in timeline if t[0] < duration]):
                continue
            critical_timeline = [t for t in timeline if t[1]]

            current_buffs = filter_status(current_status, school, skill_id)
            snapshot_buffs = filter_status(snapshot_status, school, skill_id)
            buffs = concat_buffs(current_buffs, snapshot_buffs)

            if not (detail := skill_detail.get(buffs)):
                add_buffs(current_buffs, snapshot_buffs, attribute, skill)
                detail = skill_detail[buffs] = Detail(*skill(attribute))
                detail.gradients = analyze_gradients(skill, attribute)
                sub_buffs(current_buffs, snapshot_buffs, attribute, skill)

            detail.critical_count += len(critical_timeline)
            detail.count += len(timeline)
            skill_total.critical_count += len(critical_timeline)
            skill_total.count += len(timeline)

            skill_total.damage += detail.damage * len(timeline)
            skill_total.critical_damage += detail.critical_damage * len(timeline)
            skill_total.expected_damage += detail.expected_damage * len(timeline)
            skill_total.critical_strike += detail.critical_strike * len(timeline)
            for attr, residual_damage in detail.gradients.items():
                skill_total.gradients[attr] += residual_damage * len(timeline)

        if skill_total.count:
            total.expected_damage += skill_total.expected_damage
            skill_summary.expected_damage += skill_total.expected_damage
            skill_summary.critical_count += skill_total.critical_strike
            skill_summary.count += skill_total.count
            skill_total.damage /= skill_total.count
            skill_total.critical_damage /= skill_total.count
            skill_total.expected_damage /= skill_total.count
            skill_total.critical_strike /= skill_total.count
            for attr, residual_damage in skill_total.gradients.items():
                total.gradients[attr] += residual_damage
                skill_total.gradients[attr] /= skill_total.count
        else:
            details.pop(skill.display_name)

    summary = {skill: detail for skill, detail in summary.items() if detail.count}
    return total, summary, details


def analyze_gradients(skill, attribute):
    results = {}
    for attr, value in attribute.grad_attrs.items():
        origin_value = getattr(attribute, attr)
        setattr(attribute, attr, origin_value + value)
        _, _, results[attr], _ = skill(attribute)
        setattr(attribute, attr, origin_value)
    return results