File size: 7,966 Bytes
1cc60af
c145eab
 
 
 
 
 
 
 
 
1cc60af
c145eab
 
 
 
1cc60af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c145eab
 
 
 
 
 
 
d26e0de
c145eab
1cc60af
d26e0de
c145eab
 
1cc60af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c145eab
1cc60af
c145eab
 
 
1cc60af
 
 
 
 
 
c145eab
 
1cc60af
d26e0de
1cc60af
 
 
 
 
 
 
c145eab
 
1cc60af
d26e0de
1cc60af
 
 
 
 
 
c145eab
 
 
 
 
 
 
 
1cc60af
 
 
c145eab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1cc60af
c145eab
 
 
 
1cc60af
 
c145eab
1cc60af
d26e0de
1cc60af
 
 
 
 
 
 
 
 
 
 
 
c145eab
 
 
 
 
1cc60af
c145eab
d26e0de
1cc60af
 
c145eab
 
1cc60af
 
 
 
 
 
 
 
d26e0de
 
1cc60af
 
d26e0de
 
1cc60af
 
 
 
 
 
 
 
d26e0de
 
1cc60af
 
d26e0de
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import pandas as pd

from gr.components.combat import CombatComponent
from assets.constant import ATTR_TYPE_TRANSLATE
# from gr.scripts.bonuses import Bonuses
# from gr.scripts.consumables import Consumables
from gr.scripts.top import Parser
from gr.scripts.equipments import Equipments
from gr.scripts.recipes import Recipes
from gr.scripts.talents import Talents
from utils.analyzer import analyze_records

import gradio as gr

FULL_SPACE = "\u3000"
BUFF_CONCAT = ":"


class Detail:
    records: pd.DataFrame = pd.DataFrame()

    skill: str = ""
    status_set: list = []

    @property
    def current_records(self):
        records = self.records
        if self.skill:
            records = records[records.skill == self.skill]
        if self.status_set:
            for buff in self.status_set:
                column, stack = buff.split(BUFF_CONCAT)
                records = records[records[column] == int(stack)]
        return records

    @property
    def available_status(self):
        status_set = []
        records = self.current_records
        for column in records.columns:
            if "#" not in column:
                continue
            for stack in records[column].unique():
                if not stack:
                    continue
                status_set.append(f"{column}{BUFF_CONCAT}{stack:.0f}")
        return status_set


def attribute_content(display_attrs, attribute):
    content = []
    for attr, name in display_attrs.items():
        value = getattr(attribute, attr)
        if isinstance(value, int):
            content.append(name.ljust(10, FULL_SPACE) + str(value))
        else:
            content.append(name.ljust(10, FULL_SPACE) + f"{value * 100:.2f}%")
    return "\n".join(content)


def summary_content(records, total_damage):
    content = {}
    group_records = records.groupby("skill_name")
    sum_expected_damage = group_records.expected_damage.sum()
    mean_critical_strike = group_records.critical_strike.mean()
    for skill_name, count in group_records.skill_name.count().items():
        critical_strike = mean_critical_strike[skill_name]
        critical_count = critical_strike * count
        hit = 1 - critical_strike
        hit_count = count - critical_strike
        expected_damage = sum_expected_damage[skill_name]
        content[expected_damage] = (
            [f"{skill_name}/{count}",
             f"{hit_count:.2f}/{hit * 100:.2f}%", f"{critical_count:.2f}/{critical_strike * 100:.2f}%",
             f"{expected_damage:.2f}/{expected_damage / total_damage:.2f}%"]
        )
    content = [content[k] for k in sorted(content, reverse=True)]
    return content


def gradient_content(records, attribute, total_damage):
    content = []
    for attr in attribute.grad_attrs:
        gradient = records[attr].sum() / total_damage - 1
        content.append(ATTR_TYPE_TRANSLATE[attr].ljust(10, FULL_SPACE) + f"{gradient * 100:.2f}")
    return "\n".join(content)


def detail_content(records):
    damage_detail = "\n".join([
        "统计数量".ljust(10) + f"{len(records)}",
        "命中伤害".ljust(10) + f"{records.damage.mean():.0f}",
        "会心伤害".ljust(10) + f"{records.critical_damage.mean():.0f}",
        "期望伤害".ljust(10) + f"{records.expected_damage.mean():.0f}",
        "实际伤害".ljust(10) + f"{records.actual_damage.mean():.0f}",
        "期望会心".ljust(10) + f"{records.critical_strike.mean() * 100:.2f}%",
        "实际会心".ljust(10) + f"{records.actual_critical_strike.mean() * 100:.2f}%"
    ])

    # damage_gradient = gradient_content(records, attribute, records.expected_damage.sum())

    return damage_detail


def timeline_content(records):
    columns = [column for column in records.columns if "#" in column]
    return records[["time", "skill"] + columns].rename(columns={"time": "时间", "skill": "技能"})


def combat_script(
        parser: Parser,
        talents: Talents, recipes: Recipes, equipments: Equipments,
        # consumables: Consumables, bonuses: Bonuses
        combat_component: CombatComponent,
):
    detail = Detail()

    def formulate(target_level, duration):
        combat_update = {}
        school = parser.current_school

        attribute = school.attribute()
        attribute.target_level = target_level
        for attr, value in equipments.attrs.items():
            setattr(attribute, attr, getattr(attribute, attr) + value)
        combat_update[combat_component.init_attribute] = gr.update(
            value=attribute_content(school.display_attrs, attribute)
        )
        # for attr, value in consumables.attrs.items():
        #     setattr(attribute, attr, getattr(attribute, attr) + value)

        equipment_gains = [school.gains[gain] for gain in equipments.gains]
        talent_gains = [school.talent_gains[school.talent_encoder[talent]] for talent in talents.gains]
        recipe_gains = [
            school.recipe_gains[skill][recipe]
            for i, skill in enumerate(school.recipe_gains)
            for recipe in recipes.gains[i]
        ]
        gains = equipment_gains + talent_gains + recipe_gains  # + bonuses.gains

        for gain in gains:
            gain.add(attribute, school.skills, school.buffs)

        combat_update[combat_component.final_attribute] = gr.update(
            value=attribute_content(school.display_attrs, attribute)
        )
        records = analyze_records(parser, duration, attribute)

        for gain in gains:
            gain.sub(attribute, school.skills, school.buffs)

        total_damage = records.expected_damage.sum()
        combat_update[combat_component.dps] = gr.update(value=f"{total_damage / duration:.0f}")

        combat_update[combat_component.gradient] = gradient_content(records, attribute, total_damage)

        combat_update[combat_component.summary] = summary_content(records, total_damage)

        detail.records = records

        combat_update[combat_component.skill_select] = gr.update(choices=list(records.skill.unique()))
        if detail.current_records.empty:
            detail.status_set = []
        records = detail.current_records

        combat_update[combat_component.status_select] = gr.update(choices=detail.available_status)
        combat_update[combat_component.damage_detail] = gr.update(value=detail_content(records), visible=True)
        combat_update[combat_component.damage_timeline] = gr.update(value=timeline_content(records), visible=True)

        return combat_update

    combat_component.formulate.click(
        formulate,
        [combat_component.target_level, combat_component.combat_duration],
        [combat_component.init_attribute, combat_component.final_attribute,
         combat_component.dps, combat_component.gradient, combat_component.summary,
         combat_component.skill_select, combat_component.status_select,
         combat_component.damage_detail, combat_component.damage_timeline]
    )

    def skill_changed(skill):
        detail.skill = skill

        if detail.current_records.empty:
            detail.status_set = []

        records = detail.current_records
        return gr.update(choices=detail.available_status), detail_content(records), timeline_content(records)

    combat_component.skill_select.change(
        skill_changed, combat_component.skill_select,
        [combat_component.status_select, combat_component.damage_detail, combat_component.damage_timeline]
    )

    def status_changed(status_set):
        detail.status_set = status_set
        records = detail.current_records

        if records.empty:
            return gr.update(visible=False), gr.update(visible=False)
        return (gr.update(value=detail_content(records), visible=True),
                gr.update(value=timeline_content(records), visible=True))

    combat_component.status_select.change(
        status_changed, combat_component.status_select,
        [combat_component.damage_detail, combat_component.damage_timeline]
    )