File size: 4,665 Bytes
2452398
5825182
 
2452398
 
5825182
 
 
2452398
 
5825182
 
 
970efde
5825182
 
 
 
 
 
 
 
 
 
 
2452398
5825182
 
 
 
 
 
 
 
 
 
2452398
 
3ed500d
2452398
 
88de31c
3ed500d
2452398
 
5825182
 
 
2452398
 
 
5825182
970efde
 
 
 
 
 
 
3ed500d
 
5825182
 
 
 
2452398
970efde
2452398
 
5825182
 
2452398
 
 
 
970efde
 
 
 
2452398
970efde
2452398
 
 
 
970efde
 
2452398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970efde
2452398
 
970efde
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
from base.attribute import Attribute
from base.skill import Skill
from utils.parser import School


def filter_status(status, school: School, skill_id):
    buffs = []
    for buff_id, buff_level, buff_stack in status:
        buff = school.buffs[buff_id]
        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(buffs)


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)
    else:
        for buff in snapshot_buffs:
            buff.add_snapshot(attribute, skill)
        for buff in current_buffs:
            buff.add_current(attribute, skill)


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)
    else:
        for buff in snapshot_buffs:
            buff.sub_snapshot(attribute, skill)
        for buff in current_buffs:
            buff.sub_current(attribute, skill)


def analyze_details(record, duration: int, attribute: Attribute, school: School):
    details = {}
    total_damage = 0
    total_gradients = {attr: 0. for attr in attribute.grad_attrs}
    duration *= 1000

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

        skill_detail = {}
        details[skill.display_name] = skill_detail
        for (current_status, snapshot_status), timeline in status.items():
            hit_timeline, critical_timeline = [], []
            for timestamp, critical in timeline:
                if critical:
                    critical_timeline.append(timestamp)
                else:
                    hit_timeline.append(timestamp)
            timeline = [t for t in timeline if t[0] < duration]
            if not timeline:
                continue

            current_buffs = filter_status(current_status, school, skill_id)
            snapshot_buffs = filter_status(snapshot_status, school, skill_id)
            add_buffs(current_buffs, snapshot_buffs, attribute, skill)

            damage, expected_critical_strike, critical_damage, expected_damage = skill(attribute)
            gradients = analyze_gradients(skill, attribute)

            sub_buffs(current_buffs, snapshot_buffs, attribute, skill)

            total_damage += expected_damage * len(timeline)
            for attr, residual_damage in gradients.items():
                total_gradients[attr] += residual_damage * len(timeline)

            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 = "~"
            skill_detail[buffs] = dict(
                damage=damage,
                critical_damage=critical_damage,
                expected_damage=expected_damage,
                critical_strike=len(critical_timeline) / (len(critical_timeline) + len(hit_timeline)),
                expected_critical_strike=expected_critical_strike,
                # "timeline": [round(t / 1000, 3) for t in timeline],
                count=len(timeline),
                gradients=gradients
            )

    for attr, residual_damage in total_gradients.items():
        total_gradients[attr] = round(residual_damage / total_damage * 100, 4)

    summary = analyze_summary(details)
    return total_damage, total_gradients, details, summary


def analyze_summary(details):
    summary = {}
    for skill, skill_detail in details.items():
        skill = skill.split("/")[0]
        if skill not in summary:
            summary[skill] = {"count": 0, "critical": 0, "damage": 0}
        for buff, detail in skill_detail.items():
            summary[skill]["count"] += detail['count']
            summary[skill]["critical"] += detail['count'] * detail['expected_critical_strike']
            summary[skill]["damage"] += detail['count'] * detail['expected_damage']

    return summary


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