endfield-py 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
endfield/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .client import Endfield
2
+
3
+ __all__ = ["Endfield"]
endfield/calculator.py ADDED
@@ -0,0 +1,271 @@
1
+ from typing import Optional, Union, List, Dict, Literal
2
+
3
+ from .models.character import (
4
+ ComputedStats, TalentInfo, SkillInfo, CharacterData,
5
+ TalentPassiveNode, TalentFactoryNode,
6
+ )
7
+ from .models.equipment import EquipData, SuitSet
8
+ from .models.weapon import WeaponData
9
+
10
+
11
+ F_TYPES = Literal["BaseAddition", "BaseMultiplier", "BaseFinalAddition", "BaseFinalMultiplier"]
12
+ ATTRI_IDS = Literal["39", "40", "41", "42"]
13
+
14
+ ID_TO_PROP: Dict[str, str] = {
15
+ "1": "MaxHp_base",
16
+ "2": "Atk_base",
17
+ "3": "Def_base",
18
+ "9": "CriticalRate_base",
19
+ "10": "CriticalDamage_base",
20
+ "17": "NormalAtkDamageIncrease_base",
21
+ "28": "UltimateSkillDamageIncrease_base",
22
+ "29": "HealOutputIncrease_base",
23
+ "30": "HealTakenIncrease_base",
24
+ "32": "NormalSkillDamageIncrease_base",
25
+ "33": "ComboSkillDamageIncrease_base",
26
+ "39": "Str_base",
27
+ "40": "Agi_base",
28
+ "41": "Wisd_base",
29
+ "42": "Will_base",
30
+ "44": "UltimateSpGainScalar_base",
31
+ "50": "PhysicalDamageIncrease_base",
32
+ "51": "FireDamageIncrease_base",
33
+ "52": "PulseDamageIncrease_base",
34
+ "53": "CrystDamageIncrease_base",
35
+ "54": "NaturalDamageIncrease_base",
36
+ "55": "EtherDamageIncrease_base",
37
+ "87": "PhysicalAndSpellInflictionEnhance_base",
38
+ "10000": "Main_ratio",
39
+ "10001": "Sub_ratio",
40
+ }
41
+
42
+ ID_TO_OBJ_MAP: Dict[str, str] = {
43
+ "1": "hp",
44
+ "2": "atk",
45
+ "3": "defense",
46
+ "9": "crit_rate",
47
+ "10": "crit_dmg",
48
+ "17": "normal_atk_dmg_bonus",
49
+ "28": "ult_skill_dmg_bonus",
50
+ "29": "healing_bonus",
51
+ "30": "healing_received_bonus",
52
+ "32": "normal_skill_dmg_bonus",
53
+ "33": "combo_skill_dmg_bonus",
54
+ "39": "str",
55
+ "40": "agi",
56
+ "41": "wisd",
57
+ "42": "will",
58
+ "44": "ultimate_gain_efficiency",
59
+ "50": "physical_dmg_bonus",
60
+ "51": "fire_dmg_bonus",
61
+ "52": "pulse_dmg_bonus",
62
+ "53": "cryst_dmg_bonus",
63
+ "54": "natural_dmg_bonus",
64
+ "55": "ether_dmg_bonus",
65
+ "87": "infliction_enhance",
66
+ "10000": "main_attri_ratio",
67
+ "10001": "sub_attri_ratio",
68
+ }
69
+
70
+ INT_FIELDS = {"hp", "atk", "defense", "str", "agi", "wisd", "will"}
71
+ CHAR_ATTRI = {"39", "40", "41", "42"}
72
+ SPECIAL_IDS = {"10000", "10001"}
73
+
74
+
75
+ class _HpState:
76
+ __slots__ = ("mult", "flat", "final_mult")
77
+
78
+ def __init__(self):
79
+ self.mult: float = 0.0
80
+ self.flat: float = 0.0
81
+ self.final_mult: float = 0.0
82
+
83
+
84
+
85
+ def apply_prop(
86
+ computed: ComputedStats,
87
+ prop_id: str,
88
+ value: int | float,
89
+ formula: F_TYPES = "BaseAddition",
90
+ main_attri_id: ATTRI_IDS = "39",
91
+ sub_attri_id: ATTRI_IDS = "40",
92
+ base_attri_value: int | float = 0,
93
+ sub_attri_value: int | float = 0,
94
+ base_atk: int = 0,
95
+ base_hp: int = 0,
96
+ base_attributes: dict | None = None,
97
+ hp_state: _HpState | None = None,
98
+ ) -> None:
99
+
100
+ if prop_id in SPECIAL_IDS:
101
+ attri_id = main_attri_id if prop_id == "10000" else sub_attri_id
102
+ if attri_id not in CHAR_ATTRI:
103
+ return
104
+ base_val = base_attri_value if prop_id == "10000" else sub_attri_value
105
+ obj = ID_TO_OBJ_MAP[attri_id]
106
+ cur = getattr(computed, obj, 0) or 0
107
+ if value <= 1.5 or formula == "BaseMultiplier":
108
+ new_val = cur + cur * value
109
+ else:
110
+ new_val = cur + value
111
+ setattr(computed, obj, int(new_val) if obj in INT_FIELDS else new_val)
112
+ return
113
+
114
+ obj = ID_TO_OBJ_MAP.get(prop_id)
115
+ if obj is None:
116
+ print(f"[apply_prop] Unknown prop_id: {prop_id}")
117
+ return
118
+
119
+ cur = getattr(computed, obj, None)
120
+ if cur is None:
121
+ cur = 0
122
+
123
+ if prop_id == "1" and hp_state is not None:
124
+ if formula == "BaseMultiplier":
125
+ hp_state.mult += value
126
+ elif formula in ("BaseAddition", "BaseFinalAddition"):
127
+ hp_state.flat += value
128
+ elif formula == "BaseFinalMultiplier":
129
+ hp_state.final_mult += value
130
+ else:
131
+ print(f"[apply_prop] Unknown HP formula: {formula}")
132
+ return
133
+
134
+ if formula == "BaseAddition":
135
+ new_val = cur + value
136
+
137
+ elif formula == "BaseMultiplier":
138
+ if prop_id == "2":
139
+ base_val = base_atk
140
+ elif prop_id == "1":
141
+ base_val = base_hp
142
+ elif base_attributes and prop_id in base_attributes:
143
+ base_val = base_attributes[prop_id].value
144
+ else:
145
+ print(f"[apply_prop] BaseMultiplier: no base value for prop_id={prop_id}")
146
+ return
147
+ if not base_val:
148
+ print(f"[apply_prop] BaseMultiplier: base_val is zero for prop_id={prop_id}")
149
+ return
150
+ new_val = cur + base_val * value
151
+
152
+ elif formula == "BaseFinalAddition":
153
+ new_val = cur + value
154
+
155
+ elif formula == "BaseFinalMultiplier":
156
+ new_val = cur + cur * value
157
+
158
+ else:
159
+ print(f"[apply_prop] Unknown formula: {formula}")
160
+ return
161
+
162
+ if obj in INT_FIELDS:
163
+ new_val = int(new_val)
164
+ setattr(computed, obj, new_val)
165
+
166
+
167
+
168
+ def compute_final_stats(character: CharacterData) -> ComputedStats:
169
+ base_atk_char = character.base_atk.value
170
+ base_hp = character.base_hp.value
171
+
172
+ main_attri_id = character.main_attribute.attri_id
173
+ sub_attri_id = character.sub_attribute.attri_id
174
+
175
+ all_base_attri = {item.attri_id: item for item in character.base_attribute}
176
+ main_attri_value = all_base_attri[main_attri_id].value
177
+ sub_attri_value = all_base_attri[sub_attri_id].value
178
+
179
+ base_str = all_base_attri["39"].value
180
+ base_agi = all_base_attri["40"].value
181
+ base_wisd = all_base_attri["41"].value
182
+ base_will = all_base_attri["42"].value
183
+
184
+ weapon_base_atk = character.weapon.base_atk
185
+ weapon_skills = {skill.skill_id: skill for skill in character.weapon.skills}
186
+
187
+ total_base_atk = base_atk_char + weapon_base_atk
188
+
189
+ computed = ComputedStats(
190
+ str=int(base_str),
191
+ agi=int(base_agi),
192
+ wisd=int(base_wisd),
193
+ will=int(base_will),
194
+ hp=int(base_hp),
195
+ atk=int(total_base_atk),
196
+ defense=0,
197
+ crit_rate=0.05,
198
+ crit_dmg=0.5,
199
+ arts_intensity=0.0,
200
+ healing_received_bonus=0.0,
201
+ ultimate_gain_efficiency=1.0,
202
+ healing_bonus=0.0,
203
+ normal_atk_dmg_bonus=0.0,
204
+ normal_skill_dmg_bonus=0.0,
205
+ combo_skill_dmg_bonus=0.0,
206
+ ult_skill_dmg_bonus=0.0,
207
+ physical_dmg_bonus=0.0,
208
+ fire_dmg_bonus=0.0,
209
+ pulse_dmg_bonus=0.0,
210
+ cryst_dmg_bonus=0.0,
211
+ natural_dmg_bonus=0.0,
212
+ ether_dmg_bonus=0.0,
213
+ infliction_enhance=0.0,
214
+ )
215
+
216
+ hp_state = _HpState()
217
+
218
+ def _apply(prop_id, value, formula):
219
+ apply_prop(
220
+ computed, prop_id, value, formula,
221
+ main_attri_id, sub_attri_id,
222
+ main_attri_value, sub_attri_value,
223
+ int(total_base_atk), int(base_hp),
224
+ all_base_attri, hp_state,
225
+ )
226
+
227
+
228
+ for equip in character.equips:
229
+ for attri in equip.attr_modifiers:
230
+ _apply(str(attri.attr_type), attri.value, attri.formula)
231
+
232
+
233
+ if character.talents and character.talents.attr_nodes:
234
+ t = character.talents.attr_nodes
235
+ _apply(t.attri_id, t.total_value, t.formula)
236
+
237
+ if character.suit_sets:
238
+ for prop in character.suit_sets.active_bonus.propmap:
239
+ _apply(prop.prop_id, prop.value, prop.formula)
240
+
241
+
242
+ if character.talents.potential_attributes:
243
+ for pote_attr in character.talents.potential_attributes:
244
+ for attr in pote_attr.attributes:
245
+ _apply(attr.attri_id, attr.value, attr.formula)
246
+
247
+ for skill in weapon_skills.values():
248
+ if type(skill.prop_id) == str:
249
+ _apply(skill.prop_id, skill.value, skill.formula)
250
+ elif type(skill.prop_id) == list:
251
+ for i , prop_id in enumerate(skill.prop_id):
252
+ val = skill.value[i]
253
+ formula = skill.formula[i]
254
+ _apply(prop_id, val, formula)
255
+
256
+ hp_before_mult = base_hp + computed.str * 5
257
+ computed.hp = int(hp_before_mult * (1.0 + hp_state.mult) + hp_state.flat)
258
+ if hp_state.final_mult:
259
+ computed.hp = int(computed.hp * (1.0 + hp_state.final_mult))
260
+
261
+
262
+ def _final_attri(attri_id: str) -> int:
263
+ return {"39": computed.str, "40": computed.agi,
264
+ "41": computed.wisd, "42": computed.will}.get(attri_id, 0)
265
+
266
+ atk_mult = 1.0 + _final_attri(main_attri_id) * 0.005 + _final_attri(sub_attri_id) * 0.002
267
+ computed.atk = int(computed.atk * atk_mult)
268
+
269
+ computed.healing_received_bonus = round(computed.will * 0.001, 6)
270
+
271
+ return computed