File size: 5,285 Bytes
b76daae
 
 
 
2452398
5825182
 
2452398
 
b76daae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5825182
 
 
2452398
b76daae
 
2452398
5825182
 
 
b76daae
5825182
 
 
 
 
 
 
 
 
 
 
2452398
5825182
 
 
 
 
 
 
 
 
 
2452398
 
b76daae
 
 
 
 
 
 
 
 
3ed500d
b76daae
2452398
b76daae
3ed500d
2452398
 
5825182
 
b76daae
 
 
 
 
 
 
 
5825182
b76daae
3ed500d
b76daae
5825182
 
 
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
from dataclasses import dataclass
from collections import defaultdict
from typing import Dict

from base.attribute import Attribute
from base.skill import Skill
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)
    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 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 *= 1000

    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_name = skill_level, skill_stack, 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)

        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

    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