🔥 独立伤害计算模块,需要安装依赖

This commit is contained in:
qwerdvd 2023-10-30 16:16:16 +08:00
parent 327d14474c
commit 6490a41030
No known key found for this signature in database
GPG Key ID: A3AF89C783404769
28 changed files with 84 additions and 9997 deletions

View File

@ -2,18 +2,18 @@ import re
from pathlib import Path from pathlib import Path
from typing import Tuple, cast from typing import Tuple, cast
from PIL import Image
from gsuid_core.sv import SV
from gsuid_core.bot import Bot from gsuid_core.bot import Bot
from gsuid_core.models import Event from gsuid_core.models import Event
from gsuid_core.sv import SV
from PIL import Image
from .to_card import api_to_card
from ..utils.convert import get_uid from ..utils.convert import get_uid
from ..utils.sr_prefix import PREFIX
from ..utils.error_reply import UID_HINT from ..utils.error_reply import UID_HINT
from .get_char_img import draw_char_info_img
from ..utils.image.convert import convert_img from ..utils.image.convert import convert_img
from ..utils.resource.RESOURCE_PATH import TEMP_PATH from ..utils.resource.RESOURCE_PATH import TEMP_PATH
from ..utils.sr_prefix import PREFIX
from .get_char_img import draw_char_info_img
from .to_card import api_to_card
sv_char_info_config = SV('sr面板设置', pm=2) sv_char_info_config = SV('sr面板设置', pm=2)
sv_get_char_info = SV('sr面板查询', priority=10) sv_get_char_info = SV('sr面板查询', priority=10)

View File

@ -1,18 +0,0 @@
from typing import Dict
from .mono.Character import Character
from .damage.Avatar import AvatarInstance
async def cal_char_info(char_data: Dict):
char: Character = Character(char_data)
await char.get_equipment_info()
await char.get_char_attribute_bonus()
await char.get_relic_info()
return char
async def cal_info(char_data: Dict):
char = await cal_char_info(char_data)
avatar = AvatarInstance(char)
return await avatar.gat_damage()

View File

@ -1,34 +0,0 @@
from ..utils.map.SR_MAP_PATH import RelicId2MainAffixGroup
from ..utils.excel.model import RelicSubAffixConfig, RelicMainAffixConfig
async def cal_relic_main_affix(
relic_id: int,
set_id: str,
affix_id: int,
relic_type: int,
relic_level: int,
):
if set_id[0] == 3:
rarity = int(str(relic_id)[0]) - 1
group_id = str(rarity) + str(relic_type)
else:
group_id = str(RelicId2MainAffixGroup[str(relic_id)])
relic_data = RelicMainAffixConfig.Relic[group_id][str(affix_id)]
base_value = relic_data.BaseValue.Value
level_add = relic_data.LevelAdd.Value
value = base_value + level_add * relic_level
affix_property = relic_data.Property
return affix_property, value
async def cal_relic_sub_affix(
relic_id: int, affix_id: int, cnt: int, step: int
):
rarity = int(str(relic_id)[0]) - 1
relic_data = RelicSubAffixConfig.Relic[str(rarity)][str(affix_id)]
base_value = relic_data.BaseValue.Value
step_value = relic_data.StepValue.Value
value = base_value * cnt + step_value * step
affix_property = relic_data.Property
return affix_property, value

View File

@ -1,117 +0,0 @@
import json
from typing import Dict
from pathlib import Path
from gsuid_core.logger import logger
from .Weapon.Weapon import Weapon
from ..mono.Character import Character
from .Base.model import DamageInstance
from .Base.AvatarBase import BaseAvatarinfo
from .Relic.Relic import RelicSet, SingleRelic
from .AvatarDamage.AvatarDamage import AvatarDamage
Excel_path = Path(__file__).parent
with Path.open(Excel_path / 'Excel' / 'SkillData.json', encoding='utf-8') as f:
skill_dict = json.load(f)
class AvatarInstance:
def __init__(self, raw_data: Character):
self.raw_data = DamageInstance(raw_data)
self.avatardamage = AvatarDamage.create(
self.raw_data.avatar, self.raw_data.skill
)
self.avatar = BaseAvatarinfo(self.raw_data.avatar)
self.weapon = Weapon.create(self.raw_data.weapon)
self.relic_set = RelicSet().create(self.raw_data.relic)
self.base_attr = self.cal_role_base_attr()
self.attribute_bonus: dict[str, float] = {}
self.cal_relic_attr_add()
self.cal_avatar_attr_add()
self.cal_avatar_eidolon_add()
self.cal_weapon_attr_add()
def merge_attribute_bonus(self, add_attribute: Dict[str, float]):
for attribute in add_attribute:
if attribute in self.attribute_bonus:
self.attribute_bonus[attribute] += add_attribute[attribute]
else:
self.attribute_bonus[attribute] = add_attribute[attribute]
def cal_role_base_attr(self):
logger.info('cal_role_base_attr')
base_attr: dict[str, float] = {}
avatar_attribute = self.avatar.avatar_attribute
for attr_name, attr_value in avatar_attribute.items():
if attr_name in base_attr:
base_attr[attr_name] += attr_value
else:
base_attr[attr_name] = attr_value
weapon_attribute = self.weapon.weapon_base_attribute
for attr_name, attr_value in weapon_attribute.items():
if attr_name in base_attr:
base_attr[attr_name] += attr_value
else:
base_attr[attr_name] = attr_value
return base_attr
def cal_relic_attr_add(self):
# 单件属性
for relic_type in self.relic_set.__dict__:
if type(self.relic_set.__dict__[relic_type]) == SingleRelic:
relic: SingleRelic = self.relic_set.__dict__[relic_type]
self.merge_attribute_bonus(relic.relic_attribute_bonus)
# 套装面板加成属性
for set_skill in self.relic_set.SetSkill:
self.merge_attribute_bonus(set_skill.relicSetAttribute)
def cal_avatar_eidolon_add(self):
self.merge_attribute_bonus(self.avatardamage.eidolon_attribute)
self.merge_attribute_bonus(self.avatardamage.extra_ability_attribute)
def cal_avatar_attr_add(self):
attribute_bonus = self.avatar.avatar_attribute_bonus
if attribute_bonus:
for bonus in attribute_bonus:
status_add = bonus.statusAdd
bonus_property = status_add.property
value = status_add.value
if bonus_property in self.attribute_bonus:
self.attribute_bonus[bonus_property] += value
else:
self.attribute_bonus[bonus_property] = value
def cal_weapon_attr_add(self):
self.merge_attribute_bonus(self.weapon.weapon_attribute)
async def gat_damage(self):
# logger.info('base_attr')
# logger.info(self.base_attr)
# logger.info('attribute_bonus')
# logger.info(self.attribute_bonus)
logger.info('检查武器战斗生效的buff')
Ultra_Use = self.avatar.Ultra_Use()
logger.info('Ultra_Use')
logger.info(Ultra_Use)
self.attribute_bonus = await self.weapon.weapon_ability(
Ultra_Use, self.base_attr, self.attribute_bonus
)
logger.info(self.attribute_bonus)
logger.info('检查遗器套装战斗生效的buff')
for set_skill in self.relic_set.SetSkill:
self.attribute_bonus = await set_skill.set_skill_ability(
self.base_attr, self.attribute_bonus
)
if self.attribute_bonus is None:
raise Exception('attribute_bonus is None')
logger.info(self.attribute_bonus)
return await self.avatardamage.getdamage(
self.base_attr, self.attribute_bonus
)

View File

@ -1,171 +0,0 @@
import json
from pathlib import Path
from abc import abstractmethod
from typing import List, Tuple, Union
import msgspec
from msgspec import Struct
from .SkillBase import BaseSkills
from ....utils.excel.model import AvatarPromotionConfig
from .model import DamageInstanceSkill, DamageInstanceAvatar
path = Path(__file__).parent.parent
with Path.open(path / 'Excel' / 'SkillData.json', encoding='utf-8') as f:
skill_dict = json.load(f)
class BaseAvatarAttribute(Struct):
attack: float
defence: float
hp: float
speed: float
CriticalChanceBase: float
CriticalDamageBase: float
BaseAggro: float
def items(self) -> List[Tuple[str, float]]:
return [
('attack', self.attack),
('defence', self.defence),
('hp', self.hp),
('speed', self.speed),
('CriticalChanceBase', self.CriticalChanceBase),
('CriticalDamageBase', self.CriticalDamageBase),
('BaseAggro', self.BaseAggro),
]
class BaseAvatarBuff:
@classmethod
def create(
cls, char: DamageInstanceAvatar, skills: List[DamageInstanceSkill]
):
cls.extra_ability_id = []
if char.extra_ability:
for extra_ability in char.extra_ability:
cls.extra_ability_id.append(extra_ability['extraAbilityId'])
return cls
@abstractmethod
async def Technique(self):
...
@abstractmethod
async def eidolons(self):
...
@abstractmethod
async def extra_ability(self):
...
class BaseAvatarinfo:
def __init__(self, char: DamageInstanceAvatar):
self.avatar_id = char.id_
self.avatar_level = char.level
self.avatar_rank = char.rank
self.avatar_element = char.element
self.avatar_promotion = char.promotion
self.avatar_attribute_bonus = char.attribute_bonus
self.avatar_extra_ability = char.extra_ability
self.avatar_attribute = self.get_attribute()
def get_attribute(self):
promotion = AvatarPromotionConfig.Avatar[str(self.avatar_id)][
str(self.avatar_promotion)
]
return BaseAvatarAttribute(
# 攻击力
attack=(
promotion.AttackBase.Value
+ promotion.AttackAdd.Value * (self.avatar_level - 1)
),
# 防御力
defence=(
promotion.DefenceBase.Value
+ promotion.DefenceAdd.Value * (self.avatar_level - 1)
),
# 血量
hp=(
promotion.HPBase.Value
+ promotion.HPAdd.Value * (self.avatar_level - 1)
),
# 速度
speed=promotion.SpeedBase.Value,
# 暴击率
CriticalChanceBase=promotion.CriticalChance.Value,
# 暴击伤害
CriticalDamageBase=promotion.CriticalDamage.Value,
# 嘲讽
BaseAggro=promotion.BaseAggro.Value,
)
def Ultra_Use(self):
skill_info = skill_dict[str(self.avatar_id)]['Ultra_Use'][0]
return msgspec.convert(skill_info, type=float)
class BaseAvatar:
def __init__(
self, char: DamageInstanceAvatar, skills: List[DamageInstanceSkill]
):
self.Skill = BaseSkills.create(char=char, skills=skills)
self.Buff = BaseAvatarBuff.create(char=char, skills=skills)
self.avatar_id = char.id_
self.avatar_level = char.level
self.avatar_rank = char.rank
self.avatar_element = char.element
self.avatar_promotion = char.promotion
self.avatar_attribute_bonus = char.attribute_bonus
self.avatar_extra_ability = char.extra_ability
self.avatar_attribute = self.get_attribute()
def get_attribute(self):
promotion = AvatarPromotionConfig.Avatar[str(self.avatar_id)][
str(self.avatar_promotion)
]
return BaseAvatarAttribute(
# 攻击力
attack=(
promotion.AttackBase.Value
+ promotion.AttackAdd.Value * (self.avatar_level - 1)
),
# 防御力
defence=(
promotion.DefenceBase.Value
+ promotion.DefenceAdd.Value * (self.avatar_level - 1)
),
# 血量
hp=(
promotion.HPBase.Value
+ promotion.HPAdd.Value * (self.avatar_level - 1)
),
# 速度
speed=promotion.SpeedBase.Value,
# 暴击率
CriticalChanceBase=promotion.CriticalChance.Value,
# 暴击伤害
CriticalDamageBase=promotion.CriticalDamage.Value,
# 嘲讽
BaseAggro=promotion.BaseAggro.Value,
)
def Skill_Info(self, skill_type: str):
skill_info = skill_dict[str(self.avatar_id)]['skillList'][skill_type]
return msgspec.convert(skill_info, type=List[Union[str, int]])
def Skill_num(self, skill: Union[str, int], skill_type: str):
skill_level = 0
if skill == 'Normal':
skill_level = self.Skill.Normal_.level - 1
if skill == 'BPSkill':
skill_level = self.Skill.BPSkill_.level - 1
if skill == 'Ultra':
skill_level = self.Skill.Ultra_.level - 1
if skill == 'Talent':
skill_level = self.Skill.Talent_.level - 1
skill_info = skill_dict[str(self.avatar_id)][skill_type][skill_level]
return msgspec.convert(skill_info, type=float)

View File

@ -1,92 +0,0 @@
from typing import Dict
from abc import abstractmethod
from gsuid_core.logger import logger
from .model import DamageInstanceRelic
from ....utils.map.SR_MAP_PATH import RelicSetSkill
from ....utils.map.model.RelicSetSkill import RelicSetStatusAdd
class SingleRelic:
def __init__(self, relic: DamageInstanceRelic):
self.raw_relic = relic
self.relic_id = relic.relicId
self.set_id = relic.SetId
self.relic_type = relic.Type
self.relic_level = relic.Level
self.relic_attribute_bonus: Dict[str, float] = {}
def get_attribute_(self):
# MainAffix
if self.raw_relic.MainAffix.Property in self.relic_attribute_bonus:
self.relic_attribute_bonus[
self.raw_relic.MainAffix.Property
] += self.raw_relic.MainAffix.Value
else:
self.relic_attribute_bonus[
self.raw_relic.MainAffix.Property
] = self.raw_relic.MainAffix.Value
# SubAffix
if self.raw_relic.SubAffixList:
for sub_affix in self.raw_relic.SubAffixList:
sub_affix_property = sub_affix.Property
value = sub_affix.Value
if sub_affix_property in self.relic_attribute_bonus:
self.relic_attribute_bonus[sub_affix_property] += value
else:
self.relic_attribute_bonus[sub_affix_property] = value
class BaseRelicSetSkill:
setId: int
pieces2: bool = False
pieces4: bool = False
def __init__(self, set_id: int, count: int):
self.setId = set_id
if count >= 2:
self.pieces2 = True
logger.info(f'Relic {set_id} 2 pieces set activated')
if count == 4:
self.pieces4 = True
logger.info(f'Relic {set_id} 4 pieces set activated')
self.relicSetAttribute = self.set_skill_property_ability()
@abstractmethod
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
...
@abstractmethod
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
战斗加成属性, set_skill_property() 互斥
"""
...
def set_skill_property_ability(self):
def add_relic_set_attribute(status_add: RelicSetStatusAdd):
set_property = status_add.Property
set_value = status_add.Value
if set_property != '':
relic_set_attribute[set_property] = (
relic_set_attribute.get(set_property, 0) + set_value
)
relic_set_attribute: Dict[str, float] = {}
if self.pieces2:
status_add = RelicSetSkill.RelicSet[str(self.setId)]['2']
if status_add:
add_relic_set_attribute(status_add)
if self.pieces4:
status_add = RelicSetSkill.RelicSet[str(self.setId)]['4']
if status_add:
add_relic_set_attribute(status_add)
return relic_set_attribute

View File

@ -1,45 +0,0 @@
import json
from typing import List
from pathlib import Path
from .model import DamageInstanceSkill, DamageInstanceAvatar
path = Path(__file__).parent.parent
with Path.open(path / 'Excel' / 'SkillData.json', encoding='utf-8') as f:
skill_dict = json.load(f)
skill_types = {
'Normal': 'Normal_',
'BPSkill': 'BPSkill_',
'Ultra': 'Ultra_',
'Maze': 'Maze_',
'': 'Talent_',
}
class SingleSkill:
def __init__(self, skill: DamageInstanceSkill):
self.id = skill.skillId
self.level = skill.skillLevel
class BaseSkills:
Normal_: SingleSkill
BPSkill_: SingleSkill
Ultra_: SingleSkill
Maze_: SingleSkill
Talent_: SingleSkill
@classmethod
def create(
cls, char: DamageInstanceAvatar, skills: List[DamageInstanceSkill]
):
for skill in skills:
skill_attack_type = skill.skillAttackType
if skill_attack_type not in skill_types:
raise ValueError(
f'Unknown skillAttackType: {skill_attack_type}'
)
setattr(cls, skill_types[skill_attack_type], SingleSkill(skill))
return cls

View File

@ -1,78 +0,0 @@
from abc import abstractmethod
from typing import Dict, List, Tuple
from msgspec import Struct
from .model import DamageInstanceWeapon
from ....utils.excel.model import EquipmentPromotionConfig
from ....utils.map.SR_MAP_PATH import EquipmentID2AbilityProperty
class BaseWeaponAttribute(Struct):
hp: float
attack: float
defence: float
def items(self) -> List[Tuple[str, float]]:
return [
('hp', self.hp),
('attack', self.attack),
('defence', self.defence),
]
class BaseWeapon:
def __init__(self, weapon: DamageInstanceWeapon):
self.weapon_id = weapon.id_
self.weapon_level = weapon.level
self.weapon_rank = weapon.rank
self.weapon_promotion = weapon.promotion
self.weapon_base_attribute = self.get_attribute()
self.weapon_attribute: Dict[str, float] = {}
self.get_attribute()
self.weapon_property_ability()
@abstractmethod
async def weapon_ability(self, base_attr: Dict, attribute_bonus: Dict):
"""
战斗加成属性, weapon_property_ability() 互斥
"""
...
def weapon_property_ability(self):
"""
面板加成属性, weapon_ability() 互斥
"""
ability_property = EquipmentID2AbilityProperty[str(self.weapon_id)]
equip_ability_property = ability_property[str(self.weapon_rank)]
for equip_ability in equip_ability_property:
property_type = equip_ability['PropertyType']
value = equip_ability['Value']['Value']
if property_type in self.weapon_attribute:
self.weapon_attribute[property_type] += value
else:
self.weapon_attribute[property_type] = value
@abstractmethod
async def check(self):
...
def get_attribute(self):
promotion = EquipmentPromotionConfig.Equipment[str(self.weapon_id)][
str(self.weapon_promotion)
]
return BaseWeaponAttribute(
hp=(
promotion.BaseHP.Value
+ promotion.BaseHPAdd.Value * (self.weapon_level - 1)
),
attack=(
promotion.BaseAttack.Value
+ promotion.BaseAttackAdd.Value * (self.weapon_level - 1)
),
defence=(
promotion.BaseDefence.Value
+ promotion.BaseDefenceAdd.Value * (self.weapon_level - 1)
),
)

View File

@ -1,103 +0,0 @@
from typing import List, Union
import msgspec
from msgspec import Struct, field
class DamageInstanceSkill(Struct):
skillId: int
skillName: str
skillEffect: str
skillAttackType: str
skillLevel: int
class DamageInstanceRelicSubAffix(Struct):
SubAffixID: int
Property: str
Name: str
Cnt: int
Step: int
Value: float
class DamageInstanceRelicMainAffix(Struct):
AffixID: int
Property: str
Name: str
Value: float
class DamageInstanceRelic(Struct):
relicId: int
relicName: str
SetId: int
SetName: str
Type: int
MainAffix: DamageInstanceRelicMainAffix
SubAffixList: Union[List[DamageInstanceRelicSubAffix], None]
Level: int = 0
class DamageInstanceWeapon(Struct):
id_: str = field(name='id')
level: int
rank: int
promotion: int
class AttributeBounsStatusAdd(Struct):
property: str
name: str
value: float
class DamageInstanceAvatarAttributeBouns(Struct):
attributeBonusId: int
attributeBonusLevel: int
statusAdd: AttributeBounsStatusAdd
class DamageInstanceAvatar(Struct):
id_: str = field(name='id')
level: int
rank: int
element: str
promotion: int
attribute_bonus: Union[List[DamageInstanceAvatarAttributeBouns], None]
extra_ability: Union[List, None]
class DamageInstance:
avatar: DamageInstanceAvatar
weapon: DamageInstanceWeapon
relic: List[DamageInstanceRelic]
skill: List[DamageInstanceSkill]
def __init__(self, char):
self.avatar = DamageInstanceAvatar(
id_=char.char_id,
level=char.char_level,
rank=char.char_rank,
element=char.char_element,
promotion=char.char_promotion,
attribute_bonus=msgspec.convert(
char.attribute_bonus,
Union[List[DamageInstanceAvatarAttributeBouns], None],
),
extra_ability=msgspec.convert(
char.extra_ability, Union[List, None]
),
)
self.weapon = DamageInstanceWeapon(
id_=char.equipment['equipmentID'],
level=char.equipment['equipmentLevel'],
rank=char.equipment['equipmentRank'],
promotion=char.equipment['equipmentPromotion'],
)
self.relic = []
for relic in char.char_relic:
self.relic.append(msgspec.convert(relic, DamageInstanceRelic))
self.skill = []
for skill in char.char_skill:
self.skill.append(msgspec.convert(skill, DamageInstanceSkill))

View File

@ -1,576 +0,0 @@
{
"23001": {
"Param": {
"CriticalChance": [
0.18000000016763806, 0.21000000019557774, 0.24000000022351742,
0.2700000002514571, 0.3000000002793968
],
"a_dmg": [
0.060000000055879354, 0.07000000006519258, 0.0800000000745058,
0.09000000008381903, 0.10000000009313226
],
"e_dmg": [
0.060000000055879354, 0.07000000006519258, 0.0800000000745058,
0.09000000008381903, 0.10000000009313226
],
"q_crit_dmg": [
0.12000000011175871, 0.14000000013038516, 0.1600000001490116,
0.18000000016763806, 0.20000000018626451
]
},
"AbilityProperty": [
0.18000000016763806, 0.21000000019557774, 0.24000000022351742,
0.2700000002514571, 0.3000000002793968
]
},
"21003": {
"Param": {
"CriticalChance": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"24001": {
"Param": {
"CriticalChance": [
0.0800000000745058, 0.10000000009313226, 0.12000000011175871,
0.14000000013038516, 0.1600000001490116
],
"AttackAddedRatio": [
0.20000000018626451, 0.25000000023283064, 0.3000000002793968,
0.3500000003259629, 0.40000000037252903
]
},
"AbilityProperty": [
0.0800000000745058, 0.10000000009313226, 0.12000000011175871,
0.14000000013038516, 0.1600000001490116
]
},
"21024": {
"Param": {
"SpeedAddedRatio": [
0.0800000000745058, 0.09000000008381903, 0.10000000009313226,
0.11000000010244548, 0.12000000011175871
],
"AllDamageAddedRatio": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"21017": {
"Param": {
"a_dmg": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
],
"e_dmg": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
]
}
},
"23010": {
"Param": {
"CriticalDamageBase": [
0.36000000022351742, 0.4200000002793968, 0.4800000003352761,
0.5400000003911555, 0.60000000044703484
],
"e_dmg": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
],
"r_dmg": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
],
"t_dmg": [
0.48000000022351742, 0.5600000002793968, 0.6400000003352761,
0.7200000003911555, 0.80000000044703484
]
}
},
"23018": {
"Param": {
"r_dmg": [
0.0036000001709908247, 0.00419999985024333, 0.004800000227987766,
0.005399999907240272, 0.005999999586492777
]
}
},
"20002": {
"Param": {
"a_dmg": [
0.20000000022351742, 0.2500000002793968, 0.3000000003352761,
0.3500000003911555, 0.40000000044703484
],
"e_dmg": [
0.20000000022351742, 0.2500000002793968, 0.3000000003352761,
0.3500000003911555, 0.40000000044703484
]
}
},
"21006": {
"Param": {
"t_dmg": [
0.48000000022351742, 0.6000000002793968, 0.7200000003352761,
0.8400000003911555, 0.96000000044703484
]
}
},
"21012": {
"Param": {
"AllDamageAddedRatio": [
0.40000000022351742, 0.5000000002793968, 0.6000000003352761,
0.7000000003911555, 0.80000000044703484
]
}
},
"20011": {
"Param": {
"AllDamageAddedRatio": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
]
}
},
"20004": {
"Param": {
"StatusProbability": [
0.20000000022351742, 0.2500000002793968, 0.3000000003352761,
0.3500000003911555, 0.40000000044703484
]
}
},
"20020": {
"Param": {
"A3_AttackAddedRatio": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
]
}
},
"21013": {
"Param": {
"r_dmg": [
0.32000000022351742, 0.4000000002793968, 0.4800000003352761,
0.5600000003911555, 0.64000000044703484
]
}
},
"20006": {
"Param": {
"r_dmg": [
0.28000000022351742, 0.3500000002793968, 0.4200000003352761,
0.4900000003911555, 0.56000000044703484
]
}
},
"20014": {
"Param": {
"SpeedAddedRatio": [
0.10000000009313226, 0.12000000011175871, 0.14000000013038516,
0.1600000001490116, 0.18000000016763806
]
}
},
"20007": {
"Param": {
"AttackAddedRatio": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
]
}
},
"23000": {
"Param": {
"AttackAddedRatio": [
0.09000000022351742, 0.1050000002793968, 0.1200000003352761,
0.1350000003911555, 0.15000000044703484
],
"AllDamageAddedRatio": [
0.30000000022351742, 0.3500000002793968, 0.4000000003352761,
0.4500000003911555, 0.50000000044703484
]
}
},
"21001": {
"Param": {
"AllDamageAddedRatio": [
0.12000000022351742, 0.1500000002793968, 0.1800000003352761,
0.2100000003911555, 0.24000000044703484
]
}
},
"23011": {
"Param": {
"AllDamageAddedRatio": [
0.09000000022351742, 0.1050000002793968, 0.1200000003352761,
0.1350000003911555, 0.15000000044703484
]
}
},
"23005": {
"Param": {
"DefenceAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"20003": {
"Param": {
"DefenceAddedRatio": [
0.1600000000745058, 0.20000000009313226, 0.24000000011175871,
0.28000000013038516, 0.3200000001490116
]
}
},
"21022": {
"Param": {
"AllDamageAddedRatio": [
0.16000000022351742, 0.2050000002793968, 0.2400000003352761,
0.2850000003911555, 0.32000000044703484
]
}
},
"21015": {
"Param": {
"ignore_defence": [
0.12000000022351742, 0.1300000002793968, 0.1400000003352761,
0.1500000003911555, 0.16000000044703484
]
}
},
"23004": {
"Param": {
"AllDamageAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
],
"A2_StatusProbability": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
],
"A2_AttackAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"23007": {
"Param": {
"DmgRatio": [
0.12000000022351742, 0.1400000002793968, 0.1600000003352761,
0.1800000003911555, 0.20000000044703484
],
"CriticalChance": [
0.12000000022351742, 0.1400000002793968, 0.1600000003352761,
0.1800000003911555, 0.20000000044703484
]
}
},
"23006": {
"Param": {
"SpeedAddedRatio": [
0.04800000022351742, 0.0560000002793968, 0.0640000003352761,
0.0720000003911555, 0.0800000044703484
],
"AllDamageAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"21020": {
"Param": {
"AttackAddedRatio": [
0.16000000022351742, 0.2050000002793968, 0.2400000003352761,
0.2850000003911555, 0.32000000044703484
],
"CriticalDamageBase": [
0.24000000022351742, 0.3000000002793968, 0.3600000003352761,
0.4200000003911555, 0.48000000044703484
]
}
},
"23015": {
"Param": {
"AttackAddedRatio": [
0.18000000022351742, 0.2150000002793968, 0.2400000003352761,
0.2750000003911555, 0.30000000044703484
],
"CriticalChance": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
]
}
},
"20016": {
"Param": {
"CriticalChance": [
0.12000000022351742, 0.1500000002793968, 0.1800000003352761,
0.2100000003911555, 0.24000000044703484
]
}
},
"21005": {
"Param": {
"AttackAddedRatio": [
0.12000000022351742, 0.1500000002793968, 0.1800000003352761,
0.2100000003911555, 0.24000000044703484
]
}
},
"21019": {
"Param": {
"AttackAddedRatio": [
0.16000000022351742, 0.200000002793968, 0.2400000003352761,
0.2850000003911555, 0.32000000044703484
],
"CriticalChance": [
0.12000000022351742, 0.1500000002793968, 0.1800000003352761,
0.2100000003911555, 0.24000000044703484
]
}
},
"23009": {
"Param": {
"CriticalChance": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
],
"HPAddedRatio": [
0.18000000022351742, 0.2100000002793968, 0.2400000003352761,
0.2700000003911555, 0.30000000044703484
],
"AllDamageAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"21008": {
"Param": {
"DOTDmgAdd": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"22001": {
"Param": {
"HealRatioBase": [0.16, 0.19, 0.22, 0.25, 0.28]
}
},
"21034": {
"Param": {
"AllDamageAddedRatio": [
0.0020000022351742, 0.0025000002793968, 0.0030000003352761,
0.0035000003911555, 0.0040000044703484
]
}
},
"20009": {
"Param": {
"AllDamageAddedRatio": [
0.2000000000745058, 0.25000000009313226, 0.30000000011175871,
0.35000000013038516, 0.4100000001490116
]
}
},
"23008": {
"Param": {
"speed": [
12, 14, 16, 18, 20
]
}
},
"20019": {
"Param": {
"speed": [
12, 14, 16, 18, 20
]
}
},
"23017": {
"Param": {
"AttackAddedRatio": [
0.02399999974295497, 0.027999999467283487, 0.031999999890103936,
0.035999999614432454, 0.0400000000372529
]
}
},
"21000": {
"Param": {
"Ultra_HealRatioBase": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"21014": {
"Param": {
"StatusResistance": [
0.33000000030733645, 0.3600000003352761, 0.3900000003632158,
0.4200000003911555, 0.45000000041909516
],
"HealRatioBase": [
0.1500000001396984, 0.18000000016763806, 0.21000000019557774,
0.24000000022351742, 0.2700000002514571
]
}
},
"21032": {
"Param": {
"AttackAddedRatio": [
0.10000000009313226, 0.12499999976716936, 0.1500000001396984,
0.17499999981373549, 0.20000000018626451
],
"CriticalDamage": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"20005": {
"Param": {
"AttackAddedRatio": [
0.0800000000745058, 0.09000000008381903, 0.10000000009313226,
0.11000000010244548, 0.12000000011175871
]
}
},
"22001": {
"Param": {
"HealRatioBase": [
0.1600000001490116, 0.1900000001769513, 0.22000000020489097,
0.25000000023283064, 0.2800000002607703
]
}
},
"20001": {
"Param": {
"HealRatioBase": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"21011": {
"Param": {
"AllDamageAddedRatio": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
},
"21010": {
"Param": {
"AllDamageAddedRatio": [
0.0800000000745058, 0.10000000009313226, 0.12000000011175871,
0.14000000013038516, 0.1600000001490116
]
}
},
"21033": {
"Param": {
"AllDamageAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
],
"AttackAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"23014": {
"Param": {
"AllDamageAddedRatio": [
0.1400000000745058, 0.16400000009313226, 0.19000000011175871,
0.21400000013038516, 0.2400000001490116
],
"ResistancePenetration": [
0.1200000000745058, 0.14000000009313226, 0.16000000011175871,
0.18000000013038516, 0.2000000001490116
]
}
},
"23002": {
"Param": {
"AllDamageAddedRatio": [
0.2400000000745058, 0.28000000009313226, 0.32000000011175871,
0.36000000013038516, 0.4000000001490116
]
}
},
"21026": {
"Param": {
"AllDamageAddedRatio": [
0.16000000011175871, 0.2000000001396984, 0.24000000016763806,
0.28000000019557774, 0.32000000022351742
],
"AttackAddedRatio": [
0.1000000000745058, 0.12500000009313226, 0.15000000011175871,
0.17500000013038516, 0.2000000001490116
]
}
},
"24000": {
"Param": {
"AllDamageAddedRatio": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
],
"AttackAddedRatio": [
0.0800000000745058, 0.10000000009313226, 0.12000000011175871,
0.14000000013038516, 0.1600000001490116
]
}
},
"21027": {
"Param": {
"AllDamageAddedRatio": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
],
"AttackAddedRatio": [
0.04000000011175871, 0.0500000001396984, 0.06000000016763806,
0.07000000019557774, 0.08000000022351742
]
}
},
"23012": {
"Param": {
"CriticalChance": [
0.36000000011175871, 0.4200000001396984, 0.48000000016763806,
0.54000000019557774, 0.60000000022351742
]
}
},
"23016": {
"Param": {
"CriticalDamageBase": [
0.12000000011175871, 0.14000000013038516, 0.1600000001490116,
0.18000000016763806, 0.20000000018626451
],
"TalentDmgAdd": [
0.30000000011175871, 0.3500000001396984, 0.40000000016763806,
0.45000000019557774, 0.50000000022351742
]
}
},
"21031": {
"enable": false
},
"20000": {
"Param": {
"CriticalChance": [
0.12000000011175871, 0.1500000001396984, 0.18000000016763806,
0.21000000019557774, 0.24000000022351742
]
}
}
}

View File

@ -1,742 +0,0 @@
from collections import Counter
from typing import Dict, List, Union
from gsuid_core.logger import logger
from ..utils import merge_attribute
from ..Base.model import DamageInstanceRelic
from ..Base.RelicBase import SingleRelic, BaseRelicSetSkill
class Relic101(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
在战斗开始时
"""
logger.info('Relic101 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
pass
return attribute_bonus
class Relic102(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
"""
logger.info('Relic102 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
a_dmg = attribute_bonus.get('NormalDmgAdd', 0)
attribute_bonus['NormalDmgAdd'] = a_dmg + 0.10000000018626451
return attribute_bonus
class Relic103(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
战斗中生效:装备者提供的护盾量提高
"""
logger.info('Relic103 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
shield_added_ratio = attribute_bonus.get('shield_added_ratio', 0)
attribute_bonus['shield_added_ratio'] = (
shield_added_ratio + 0.20000000018626451
)
return attribute_bonus
class Relic104(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者施放终结技
"""
logger.info('Relic104 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
critical_damage_base = attribute_bonus.get('CriticalDamageBase', 0)
attribute_bonus['CriticalDamageBase'] = (
critical_damage_base + 0.25000000023283064
)
return attribute_bonus
class Relic105(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
施放攻击或受到攻击时, 默认叠满
"""
logger.info('Relic105 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
attack_added_ratio = attribute_bonus.get('AttackAddedRatio', 0)
attribute_bonus['AttackAddedRatio'] = (
attack_added_ratio + 0.05000000004656613 * 5
)
return attribute_bonus
class Relic106(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
"""
logger.info('Relic106 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
pass
return attribute_bonus
class Relic107(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
TODO: 检查是否是火属性伤害
"""
logger.info('Relic107 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4:
e_dmg = attribute_bonus.get('BPSkillDmgAdd', 0)
attribute_bonus['BPSkillDmgAdd'] = e_dmg + 0.12000000011175871
if self.pieces4 and await self.check(base_attr, attribute_bonus):
fire_added_ratio = attribute_bonus.get('FireAddedRatio', 0)
attribute_bonus['FireAddedRatio'] = (
fire_added_ratio + 0.12000000011175871
)
return attribute_bonus
class Relic108(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者对敌方目标造成伤害
目标拥有量子属性弱点
"""
logger.info('Relic108 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info(attribute_bonus)
ignore_defence = attribute_bonus.get('ignore_defence', 0)
attribute_bonus['ignore_defence'] = (
ignore_defence + 0.10000000009313226 * 2
)
return attribute_bonus
class Relic109(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
TODO: 检查是否释放战技
"""
logger.info('Relic109 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info(attribute_bonus)
attack_added_ratio = attribute_bonus.get('AttackAddedRatio', 0)
attribute_bonus['AttackAddedRatio'] = (
attack_added_ratio + 0.20000000018626451
)
return attribute_bonus
class Relic110(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者施放终结技
"""
logger.info('Relic110 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info('ModifyActionDelay')
return attribute_bonus
class Relic111(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
self._count = count
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者击破敌方目标弱点
"""
logger.info('Relic111 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info('ModifySPNew')
return attribute_bonus
class Relic112(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
self._count = count
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者对陷入负面效果的敌方目标造成伤害
对陷入禁锢状态的敌方目标造成伤害
"""
logger.info('Relic111 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info('对陷入负面效果的敌方目标造成伤害')
critical_chance_base = attribute_bonus.get('CriticalChanceBase', 0)
attribute_bonus['CriticalChanceBase'] = (
critical_chance_base + 0.10000000009313226
)
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info('对陷入禁锢状态的敌方目标造成伤害')
critical_damage_base = attribute_bonus.get('CriticalDamageBase', 0)
attribute_bonus['CriticalDamageBase'] = (
critical_damage_base + 0.20000000018626451
)
return attribute_bonus
class Relic113(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
self._count = count
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
当装备者受到攻击或被我方目标消耗生命值后, 暴击率提高8%, 持续2回合, 该效果最多叠加2层
"""
logger.info('Relic113 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
logger.info('当装备者受到攻击或被我方目标消耗生命值后')
critical_chance_base = attribute_bonus.get('CriticalChanceBase', 0)
attribute_bonus['CriticalChanceBase'] = (
critical_chance_base + 0.08000000009313226 * 2
)
return attribute_bonus
class Relic114(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
self._count = count
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
当装备者对我方目标施放终结技时, 我方全体速度提高12%, 持续1回合, 该效果无法叠加
"""
logger.info('Relic114 check success')
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces4 and await self.check(base_attr, attribute_bonus):
speed_added_ratio = attribute_bonus.get('SpeedAddedRatio', 0)
attribute_bonus['SpeedAddedRatio'] = (
speed_added_ratio + 0.12000000011175871
)
return attribute_bonus
class Relic301(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者的速度大于等于120
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['speed'] >= 120:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
attack_added_ratio = attribute_bonus.get('AttackAddedRatio', 0)
attribute_bonus['AttackAddedRatio'] = (
attack_added_ratio + 0.12000000011175871
)
return attribute_bonus
class Relic302(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者的速度大于等于120
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['speed'] >= 120:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
attack_added_ratio = attribute_bonus.get('AttackAddedRatio', 0)
attribute_bonus['AttackAddedRatio'] = (
attack_added_ratio + 0.0800000000745058
)
return attribute_bonus
class Relic303(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
# 提高装备者等同于当前效果命中25%的攻击力,最多提高25%
return True
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
attack_added_ratio = attribute_bonus.get('AttackAddedRatio', 0)
merged_attr = await merge_attribute(base_attr, attribute_bonus)
status_probability = merged_attr.get('StatusProbabilityBase', 0)
# 提高装备者等同于当前效果命中25%的攻击力,最多提高25%
attribute_bonus['AttackAddedRatio'] = attack_added_ratio + min(
0.25000000023283064, status_probability / 0.25
)
return attribute_bonus
class Relic304(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
备者的效果命中大于等于50%
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['StatusResistanceBase'] >= 0.5000000004656613:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
defence_added_ratio = attribute_bonus.get('DefenceAddedRatio', 0)
attribute_bonus['DefenceAddedRatio'] = (
defence_added_ratio + 0.1500000001396984
)
return attribute_bonus
class Relic305(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者的暴击伤害大于等于120%
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['CriticalDamageBase'] >= 1.2000000001862645:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
critical_chance_base = attribute_bonus.get('CriticalChanceBase', 0)
attribute_bonus['CriticalChanceBase'] = (
critical_chance_base + 0.6000000005587935
)
return attribute_bonus
class Relic306(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者当前暴击率大于等于50%
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['CriticalChanceBase'] >= 0.5:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
q_dmg = attribute_bonus.get('UltraDmgAdd', 0)
attribute_bonus['UltraDmgAdd'] = q_dmg + 0.1500000001396984
a3_dmg = attribute_bonus.get('TalentDmgAdd', 0)
attribute_bonus['TalentDmgAdd'] = a3_dmg + 0.1500000001396984
return attribute_bonus
class Relic307(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者的速度大于等于145
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['speed'] >= 145:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
break_damage_added_ratio_base = attribute_bonus.get(
'BreakDamageAddedRatioBase', 0
)
attribute_bonus['BreakDamageAddedRatioBase'] = (
break_damage_added_ratio_base + 0.20000000018626451
)
return attribute_bonus
class Relic308(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
装备者的速度大于等于120
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['speed'] >= 120:
logger.info('Relic306 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
logger.info('ModifyActionDelay')
return attribute_bonus
class Relic309(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
当装备者的当前暴击率大于等于70%, 普攻和战技造成的伤害提高20%
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['CriticalChanceBase'] >= 0.7:
logger.info('Relic309 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
a_dmg = attribute_bonus.get('NormalDmgAdd', 0)
attribute_bonus['NormalDmgAdd'] = a_dmg + 0.20000000018626451
a2_dmg = attribute_bonus.get('BPSkillDmgAdd', 0)
attribute_bonus['BPSkillDmgAdd'] = a2_dmg + 0.20000000018626451
return attribute_bonus
class Relic310(BaseRelicSetSkill):
def __init__(self, set_id: int, count: int):
super().__init__(set_id, count)
async def check(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
"""
当装备者的效果抵抗大于等于30%, 我方全体暴击伤害提高10%
"""
merged_attr = await merge_attribute(base_attr, attribute_bonus)
if merged_attr['StatusResistanceBase'] >= 0.3:
logger.info('Relic310 check success')
return True
return False
async def set_skill_ability(
self, base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
):
if self.pieces2 and await self.check(base_attr, attribute_bonus):
critical_damage_base = attribute_bonus.get('CriticalDamageBase', 0)
attribute_bonus['CriticalDamageBase'] = (
critical_damage_base + 0.10000000018626451
)
return attribute_bonus
class RelicSet:
HEAD: SingleRelic
HAND: SingleRelic
BODY: SingleRelic
FOOT: SingleRelic
NECK: SingleRelic
OBJECT: SingleRelic
Unknow: SingleRelic
SetSkill: List[
Union[
Relic101,
Relic102,
Relic103,
Relic104,
Relic105,
Relic106,
Relic107,
Relic108,
Relic109,
Relic110,
Relic111,
Relic112,
Relic113,
Relic114,
Relic301,
Relic302,
Relic303,
Relic304,
Relic305,
Relic306,
Relic307,
Relic308,
Relic309,
Relic310,
]
]
def create(self, relic_list: List[DamageInstanceRelic]):
set_id_list: List[int] = []
for relic in relic_list:
set_id_list.append(relic.SetId)
if relic.Type == 1:
self.HEAD = SingleRelic(relic)
elif relic.Type == 2:
self.HAND = SingleRelic(relic)
elif relic.Type == 3:
self.BODY = SingleRelic(relic)
elif relic.Type == 4:
self.FOOT = SingleRelic(relic)
elif relic.Type == 5:
self.NECK = SingleRelic(relic)
elif relic.Type == 6:
self.OBJECT = SingleRelic(relic)
else:
self.Unknow = SingleRelic(relic)
self.set_id_counter = Counter(set_id_list).most_common()
self.check_set()
self.get_attribute()
return self
def get_attribute(self):
for item in self.__dict__:
if type(self.__dict__[item]) == SingleRelic:
itme__: SingleRelic = self.__dict__[item]
itme__.get_attribute_()
def check_set(self):
self.SetSkill = []
for item in self.set_id_counter:
set_id = item[0]
count = item[1]
if set_id == 101:
self.SetSkill.append(Relic101(set_id, count))
elif set_id == 102:
self.SetSkill.append(Relic102(set_id, count))
elif set_id == 103:
self.SetSkill.append(Relic103(set_id, count))
elif set_id == 104:
self.SetSkill.append(Relic104(set_id, count))
elif set_id == 105:
self.SetSkill.append(Relic105(set_id, count))
elif set_id == 106:
self.SetSkill.append(Relic106(set_id, count))
elif set_id == 107:
self.SetSkill.append(Relic107(set_id, count))
elif set_id == 108:
self.SetSkill.append(Relic108(set_id, count))
elif set_id == 109:
self.SetSkill.append(Relic109(set_id, count))
elif set_id == 110:
self.SetSkill.append(Relic110(set_id, count))
elif set_id == 111:
self.SetSkill.append(Relic111(set_id, count))
elif set_id == 112:
self.SetSkill.append(Relic112(set_id, count))
elif set_id == 113:
self.SetSkill.append(Relic113(set_id, count))
elif set_id == 114:
self.SetSkill.append(Relic114(set_id, count))
elif set_id == 301:
self.SetSkill.append(Relic301(set_id, count))
elif set_id == 302:
self.SetSkill.append(Relic302(set_id, count))
elif set_id == 303:
self.SetSkill.append(Relic303(set_id, count))
elif set_id == 304:
self.SetSkill.append(Relic304(set_id, count))
elif set_id == 305:
self.SetSkill.append(Relic305(set_id, count))
elif set_id == 306:
self.SetSkill.append(Relic306(set_id, count))
elif set_id == 307:
self.SetSkill.append(Relic307(set_id, count))
elif set_id == 308:
self.SetSkill.append(Relic308(set_id, count))
elif set_id == 309:
self.SetSkill.append(Relic309(set_id, count))
elif set_id == 310:
self.SetSkill.append(Relic310(set_id, count))
else:
raise Exception(f'Unknow SetId: {set_id}')

View File

@ -1,397 +0,0 @@
import copy
from typing import Dict
from gsuid_core.logger import logger
from .utils import merge_attribute
async def calculate_heal(
base_attr: Dict[str, float],
attribute_bonus: Dict[str, float],
skill_type: str,
skill_multiplier: float,
skill_num: float,
is_atk=0,
):
add_attr_bonus = copy.deepcopy(attribute_bonus)
merged_attr = await merge_attribute(base_attr, add_attr_bonus)
if is_atk == 1:
hp = merged_attr.get('attack', 0)
else:
hp = merged_attr.get('hp', 0)
logger.info(f'生命: {hp}')
# 检查是否有治疗量加成
heal_ratio_base = merged_attr.get('HealRatioBase', 0)
for attr in merged_attr:
if '_HealRatioBase' in attr and attr.split('_')[0] in (skill_type):
heal_ratio_base += merged_attr[attr]
heal_ratio = heal_ratio_base + 1
logger.info(f'治疗量加成: {heal_ratio}')
heal_num = (hp * skill_multiplier + skill_num) * heal_ratio
return [heal_num]
async def calculate_shield(
base_attr: Dict[str, float],
attribute_bonus: Dict[str, float],
skill_multiplier: float,
skill_num: float,
is_atk=0,
):
add_attr_bonus = copy.deepcopy(attribute_bonus)
merged_attr = await merge_attribute(base_attr, add_attr_bonus)
if is_atk == 1:
defence = merged_attr.get('attack', 0)
else:
defence = merged_attr.get('defence', 0)
logger.info(f'防御力: {defence}')
# 检查是否有护盾加成
shield_added_ratio = merged_attr.get('shield_added_ratio', 0)
shield_added = shield_added_ratio + 1
logger.info(f'护盾加成: {shield_added}')
defence_num = (defence * skill_multiplier + skill_num) * shield_added
return [defence_num]
async def calculate_damage(
base_attr: Dict[str, float],
attribute_bonus: Dict[str, float],
skill_type: str,
add_skill_type: str,
element: str,
skill_multiplier: float,
level: int,
is_hp=0,
):
logger.info(f'Skill Multiplier: {skill_multiplier}')
logger.info(f'Skill Type: {skill_type}')
logger.info(f'Level: {level}')
# logger.info(f'attribute_bonus: {attribute_bonus}')
add_attr_bonus = copy.deepcopy(attribute_bonus)
add_attr_bonus = apply_attribute_bonus(
add_attr_bonus, skill_type, add_skill_type
)
merged_attr = await merge_attribute(base_attr, add_attr_bonus)
# logger.info(f'merged_attr: {merged_attr}')
if is_hp == 1:
attack = merged_attr.get('hp', 0)
elif is_hp == 2:
attack = merged_attr.get('defence', 0)
else:
attack = merged_attr.get('attack', 0)
logger.info(f'Attack: {attack}')
damage_reduction = calculate_damage_reduction(level)
logger.info(f'韧性区: {damage_reduction}')
resistance_area = calculate_resistance_area(
merged_attr, skill_type, add_skill_type, element
)
logger.info(f'抗性区: {resistance_area}')
defence_multiplier = calculate_defence_multiplier(level, merged_attr)
logger.info(f'防御区: {defence_multiplier}')
injury_area, element_area = calculate_injury_area(
merged_attr, skill_type, add_skill_type, element
)
logger.info(f'增伤区: {injury_area}')
damage_ratio = calculate_damage_ratio(
merged_attr, skill_type, add_skill_type
)
logger.info(f'易伤区: {damage_ratio}')
critical_damage = calculate_critical_damage(
merged_attr, skill_type, add_skill_type
)
logger.info(f'爆伤区: {critical_damage}')
critical_chance = calculate_critical_chance(
merged_attr, skill_type, add_skill_type
)
logger.info(f'暴击区: {critical_chance}')
expected_damage = calculate_expected_damage(
critical_chance, critical_damage
)
logger.info(f'暴击期望: {expected_damage}')
damage_cd = calculate_damage_cd(
attack,
skill_multiplier,
damage_ratio,
injury_area,
defence_multiplier,
resistance_area,
damage_reduction,
critical_damage,
)
damage_qw = calculate_damage_qw(
attack,
skill_multiplier,
damage_ratio,
injury_area,
defence_multiplier,
resistance_area,
damage_reduction,
expected_damage,
)
damage_tz = calculate_damage_tz(
attack,
skill_multiplier,
damage_ratio,
injury_area,
defence_multiplier,
resistance_area,
damage_reduction,
critical_damage,
element,
element_area,
base_attr,
)
skill_info_list = [damage_cd, damage_qw, damage_tz]
logger.info(
f'Critical Damage: {damage_cd} Expected Damage: {damage_qw} Apocalypse Damage: {damage_tz}'
)
return skill_info_list
def apply_attribute_bonus(
add_attr_bonus: Dict[str, float],
skill_type: str,
add_skill_type: str,
):
# Apply attribute bonuses to attack and status probability
for attr in add_attr_bonus:
if 'AttackAddedRatio' in attr and attr.split('AttackAddedRatio')[
0
] in (skill_type, add_skill_type):
add_attr_bonus['AttackAddedRatio'] += add_attr_bonus[attr]
if 'StatusProbabilityBase' in attr and attr.split(
'StatusProbabilityBase'
)[0] in (skill_type, add_skill_type):
add_attr_bonus['StatusProbabilityBase'] += add_attr_bonus[attr]
return add_attr_bonus
def calculate_damage_reduction(level: int):
enemy_damage_reduction = 0.1
return 1 - enemy_damage_reduction
def calculate_resistance_area(
merged_attr: Dict[str, float],
skill_type: str,
add_skill_type: str,
element: str,
):
enemy_status_resistance = 0.0
for attr in merged_attr:
if 'ResistancePenetration' in attr:
# 检查是否有某一属性的抗性穿透
attr_name = attr.split('ResistancePenetration')[0]
if attr_name in (element, 'AllDamage'):
# logger.info(f'{attr_name}属性有{merged_attr[attr]}穿透加成')
enemy_status_resistance += merged_attr[attr]
# 检查是否有某一技能属性的抗性穿透
if '_' in attr_name:
skill_name = attr_name.split('_')[0]
skillattr_name = attr_name.split('_')[1]
if skill_name == add_skill_type and skillattr_name in (
element,
'AllDamage',
):
enemy_status_resistance += merged_attr[attr]
# logger.info(
# f'{skill_name}对{skillattr_name}属性有{merged_attr[attr]}穿透加成'
# )
return 1.0 - (0 - enemy_status_resistance)
def calculate_defence_multiplier(
level: int,
merged_attr: Dict[str, float],
):
ignore_defence = merged_attr.get('ignore_defence', 0.0)
enemy_defence = (level * 10 + 200) * (1 - ignore_defence)
return (level * 10 + 200) / (level * 10 + 200 + enemy_defence)
def calculate_injury_area(
merged_attr: Dict[str, float],
skill_type: str,
add_skill_type: str,
element: str,
):
injury_area = 0.0
element_area = 0.0
for attr in merged_attr:
attr_name = attr.split('AddedRatio')[0]
skill_name = attr.split('DmgAdd')[0]
if 'DmgAdd' in attr and skill_name in (
skill_type,
add_skill_type,
):
# logger.info(
# f'{attr} 对 {skill_type} 有 {merged_attr[attr]} 伤害加成'
# )
injury_area += merged_attr[attr]
if 'AddedRatio' in attr and attr_name in (
element,
'AllDamage',
):
# logger.info(
# f'{attr} 对 {element} 属性有 {merged_attr[attr]} 伤害加成'
# )
if attr_name == element:
element_area += merged_attr[attr]
injury_area += merged_attr[attr]
return injury_area + 1, element_area
def calculate_damage_ratio(
merged_attr: Dict[str, float],
skill_type: str,
add_skill_type: str,
):
damage_ratio = merged_attr.get('DmgRatio', 0)
for attr in merged_attr:
if '_DmgRatio' in attr and attr.split('_')[0] in (
skill_type,
add_skill_type,
):
damage_ratio += merged_attr[attr]
return damage_ratio + 1
def calculate_critical_damage(
merged_attr: Dict[str, float],
skill_type: str,
add_skill_type: str,
):
if skill_type == 'DOT':
return 1.0
critical_damage_base = merged_attr.get('CriticalDamageBase', 0)
for attr in merged_attr:
if '_CriticalDamageBase' in attr and attr.split('_')[0] in (
skill_type,
add_skill_type,
):
critical_damage_base += merged_attr[attr]
return critical_damage_base + 1
def calculate_critical_chance(
merged_attr: Dict[str, float],
skill_type: str,
add_skill_type: str,
):
critical_chance_base = merged_attr['CriticalChanceBase']
for attr in merged_attr:
if '_CriticalChance' in attr and attr.split('_')[0] in (
skill_type,
add_skill_type,
):
critical_chance_base += merged_attr[attr]
return min(1, critical_chance_base)
def calculate_expected_damage(
critical_chance_base: float,
critical_damage_base: float,
):
return critical_chance_base * (critical_damage_base - 1) + 1
def calculate_damage_cd(
attack: float,
skill_multiplier: float,
damage_ratio: float,
injury_area: float,
defence_multiplier: float,
resistance_area: float,
damage_reduction: float,
critical_damage: float,
):
return (
attack
* skill_multiplier
* damage_ratio
* injury_area
* defence_multiplier
* resistance_area
* damage_reduction
* critical_damage
)
def calculate_damage_qw(
attack: float,
skill_multiplier: float,
damage_ratio: float,
injury_area: float,
defence_multiplier: float,
resistance_area: float,
damage_reduction: float,
expected_damage: float,
):
return (
attack
* skill_multiplier
* damage_ratio
* injury_area
* defence_multiplier
* resistance_area
* damage_reduction
* expected_damage
)
def calculate_damage_tz(
attack: float,
skill_multiplier: float,
damage_ratio: float,
injury_area: float,
defence_multiplier: float,
resistance_area: float,
damage_reduction: float,
critical_damage: float,
element: str,
element_area: float,
base_attr: Dict[str, float],
):
injury_add_tz = 0.0
attack_tz = attack + 355 + base_attr['attack'] * 2.334
# logger.info(f'attack_tz: {attack_tz}')
if element == 'Imaginary':
injury_add_tz = 0.12
return (
attack_tz
* skill_multiplier
* damage_ratio
* (injury_area + injury_add_tz + 2.626)
* defence_multiplier
* resistance_area
* damage_reduction
* (critical_damage + 1.794)
* 10
)

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
from typing import Dict
from gsuid_core.logger import logger
async def merge_attribute(
base_attr: Dict[str, float], attribute_bonus: Dict[str, float]
) -> Dict[str, float]:
merged_attr = base_attr.copy()
for attribute, value in attribute_bonus.items():
if attribute.endswith('Delta'):
attr = attribute.split('Delta')[0].lower()
if attr in merged_attr:
merged_attr[attr] += value
else:
merged_attr[attribute] = attribute_bonus[attribute]
elif attribute.endswith('AddedRatio'):
attr = attribute.split('AddedRatio')[0].lower()
if attr in merged_attr:
merged_attr[attr] += base_attr[attr] * value
else:
merged_attr[attribute] = attribute_bonus[attribute]
elif attribute in [
'ignore_defence',
'Atk_buff',
'Normal_buff',
'shield_added_ratio',
]:
merged_attr[attribute] = base_attr.get(attribute, 0) + value
elif attribute.endswith(
('ResistancePenetration', 'DmgAdd', 'DmgRatio')
):
merged_attr[attribute] = base_attr.get(attribute, 0) + value
elif attribute.endswith('Base'):
merged_attr[attribute] = base_attr.get(attribute, 0) + value
else:
logger.info(f'未知的属性加成: {attribute}, 采用覆盖模式')
merged_attr[attribute] = attribute_bonus[attribute]
return merged_attr

View File

@ -9,6 +9,8 @@ from gsuid_core.logger import logger
from gsuid_core.utils.image.convert import convert_img from gsuid_core.utils.image.convert import convert_img
from gsuid_core.utils.image.image_tools import draw_text_by_line from gsuid_core.utils.image.image_tools import draw_text_by_line
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from starrail_damage_cal.cal_damage import cal_char_info, cal_info
from starrail_damage_cal.to_data import api_to_dict
from ..utils.error_reply import CHAR_HINT from ..utils.error_reply import CHAR_HINT
from ..utils.excel.read_excel import light_cone_ranks from ..utils.excel.read_excel import light_cone_ranks
@ -36,10 +38,8 @@ from ..utils.resource.RESOURCE_PATH import (
SKILL_PATH, SKILL_PATH,
WEAPON_PATH, WEAPON_PATH,
) )
from .cal_damage import cal_char_info, cal_info
from .to_data import api_to_dict
Excel_path = Path(__file__).parent / 'damage' Excel_path = Path(__file__).parent
with Path.open(Excel_path / 'Excel' / 'SkillData.json', encoding='utf-8') as f: with Path.open(Excel_path / 'Excel' / 'SkillData.json', encoding='utf-8') as f:
skill_dict = json.load(f) skill_dict = json.load(f)
@ -367,8 +367,10 @@ async def draw_char_img(char_data: Dict, sr_uid: str, msg: str):
if char.equipment != {}: if char.equipment != {}:
weapon_bg = Image.open(TEXT_PATH / 'weapon_bg.png') weapon_bg = Image.open(TEXT_PATH / 'weapon_bg.png')
weapon_id = char.equipment['equipmentID'] weapon_id = char.equipment['equipmentID']
weapon_img = Image.open(WEAPON_PATH / f'{weapon_id}.png').convert('RGBA').resize( weapon_img = (
(170, 180) Image.open(WEAPON_PATH / f'{weapon_id}.png')
.convert('RGBA')
.resize((170, 180))
) )
weapon_bg.paste(weapon_img, (20, 90), weapon_img) weapon_bg.paste(weapon_img, (20, 90), weapon_img)
weapon_bg_draw = ImageDraw.Draw(weapon_bg) weapon_bg_draw = ImageDraw.Draw(weapon_bg)
@ -757,11 +759,13 @@ async def get_char_data(
elif enable_self and char_self_path.exists(): elif enable_self and char_self_path.exists():
path = char_self_path path = char_self_path
else: else:
char_data_list = await api_to_dict(sr_uid) char_id_list, _ = await api_to_dict(
sr_uid, save_path=PLAYER_PATH
)
charname_list = [] charname_list = []
if isinstance(char_data_list, str): if isinstance(char_id_list, str):
return char_data_list return char_id_list
for char in char_data_list: for char in char_id_list:
charname = avatarId2Name[str(char)] charname = avatarId2Name[str(char)]
charname_list.append(charname) charname_list.append(charname)
if str(char_name) in charname_list: if str(char_name) in charname_list:

View File

@ -1,36 +1,40 @@
import re
import json import json
import re
from pathlib import Path from pathlib import Path
from typing import Dict, Tuple, Union, Optional from typing import Dict, Optional, Tuple, Union
from gsuid_core.logger import logger from gsuid_core.logger import logger
from starrail_damage_cal.excel.model import (
from .to_data import api_to_dict AvatarPromotionConfig,
from .draw_char_img import draw_char_img EquipmentPromotionConfig,
from ..utils.error_reply import CHAR_HINT
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH
from ..utils.excel.model import AvatarPromotionConfig, EquipmentPromotionConfig
from ..utils.map.name_covert import (
name_to_avatar_id,
name_to_weapon_id,
alias_to_char_name,
alias_to_weapon_name,
) )
from starrail_damage_cal.to_data import api_to_dict
from ..utils.map.SR_MAP_PATH import ( from ..utils.map.SR_MAP_PATH import (
Property2Name,
EquipmentID2Name,
AvatarRankSkillUp, AvatarRankSkillUp,
EquipmentID2Name,
EquipmentID2Rarity, EquipmentID2Rarity,
rankId2Name, Property2Name,
skillId2Name, avatarId2DamageType,
avatarId2Name,
skillId2Effect,
avatarId2EnName, avatarId2EnName,
avatarId2Name,
avatarId2Rarity, avatarId2Rarity,
characterSkillTree, characterSkillTree,
rankId2Name,
skillId2AttackType, skillId2AttackType,
avatarId2DamageType, skillId2Effect,
skillId2Name,
) )
from ..utils.error_reply import CHAR_HINT
from ..utils.map.name_covert import (
alias_to_char_name,
alias_to_weapon_name,
name_to_avatar_id,
name_to_weapon_id,
)
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH
from .draw_char_img import draw_char_img
WEAPON_TO_INT = { WEAPON_TO_INT = {
'': 1, '': 1,
@ -130,9 +134,7 @@ async def get_char_args(
if isinstance(char_data, str): if isinstance(char_data, str):
return char_data return char_data
else: else:
for i, s in enumerate( for i, s in enumerate(['头部', '手部', '躯干', '腿部', '位面球', '连结绳']):
['头部', '手部', '躯干', '腿部', '位面球', '连结绳']
):
if '赤沙' in part: if '赤沙' in part:
continue continue
if part[-1] in PieceName_ilst[i]: if part[-1] in PieceName_ilst[i]:
@ -228,11 +230,11 @@ async def get_char_data(
elif enable_self and char_self_path.exists(): elif enable_self and char_self_path.exists():
path = char_self_path path = char_self_path
else: else:
char_data_list = await api_to_dict(sr_uid) char_id_list, _ = await api_to_dict(sr_uid, save_path=PLAYER_PATH)
charname_list = [] charname_list = []
if isinstance(char_data_list, str): if isinstance(char_id_list, str):
return char_data_list return char_id_list
for char in char_data_list: for char in char_id_list:
charname = avatarId2Name[str(char)] charname = avatarId2Name[str(char)]
charname_list.append(charname) charname_list.append(charname)
if str(char_name) in charname_list: if str(char_name) in charname_list:

View File

@ -1,113 +0,0 @@
from typing import Dict, List
from collections import Counter
from ...utils.map.SR_MAP_PATH import RelicSetSkill, EquipmentID2AbilityProperty
class Character:
def __init__(self, card_prop: Dict):
self.char_level: int = int(card_prop['avatarLevel'])
self.char_id: str = card_prop['avatarId']
self.char_name: str = card_prop['avatarName']
self.char_rank = card_prop['rank'] if card_prop.get('rank') else 0
self.char_rarity = card_prop['avatarRarity']
self.char_element = card_prop['avatarElement']
self.char_promotion = card_prop['avatarPromotion']
self.char_skill = card_prop['avatarSkill']
self.extra_ability = card_prop['avatarExtraAbility']
self.attribute_bonus = card_prop['avatarAttributeBonus']
self.char_relic = card_prop['RelicInfo']
self.base_attributes = card_prop['baseAttributes']
self.add_attr = {}
self.equipment = card_prop['equipmentInfo']
self.rarity = card_prop['avatarRarity']
self.eidolons = (
card_prop['rankList'] if card_prop.get('rankList') else []
)
async def get_equipment_info(self):
if self.equipment == {}:
return
base_attr = self.base_attributes
equip = self.equipment
ability_property = EquipmentID2AbilityProperty[
str(equip['equipmentID'])
]
equip_rank = equip['equipmentRank']
equip_ability_property = ability_property[str(equip_rank)]
equip_add_base_attr = equip['baseAttributes']
base_attr['hp'] = base_attr['hp'] + equip_add_base_attr['hp']
base_attr['attack'] = (
base_attr['attack'] + equip_add_base_attr['attack']
)
base_attr['defence'] = (
base_attr['defence'] + equip_add_base_attr['defence']
)
self.base_attributes = base_attr
for equip_ability in equip_ability_property:
property_type = equip_ability['PropertyType']
value = equip_ability['Value']['Value']
self.add_attr[property_type] = value + self.add_attr.get(
property_type, 0
)
async def get_char_attribute_bonus(self):
attribute_bonus = self.attribute_bonus
for bonus in attribute_bonus:
status_add = bonus['statusAdd']
bonus_property = status_add['property']
value = status_add['value']
self.add_attr[bonus_property] = value + self.add_attr.get(
bonus_property, 0
)
async def get_relic_info(self):
# 计算圣遗物效果
set_id_list: List[int] = []
for relic in self.char_relic:
set_id_list.append(relic['SetId'])
# 处理主属性
relic_property = relic['MainAffix']['Property']
property_value = relic['MainAffix']['Value']
self.add_attr[relic_property] = property_value + self.add_attr.get(
relic_property, 0
)
# 处理副词条
for sub in relic['SubAffixList']:
sub_property = sub['Property']
sub_value = sub['Value']
self.add_attr[sub_property] = sub_value + self.add_attr.get(
sub_property, 0
)
# 处理套装属性
set_id_dict = Counter(set_id_list)
# logger.info(set_id_dict.most_common())
for item in set_id_dict.most_common():
set_property = ''
set_id = item[0]
count = item[1]
set_value = 0
if count >= 2:
status_add = RelicSetSkill.RelicSet[str(set_id)]['2']
if status_add:
set_property = status_add.Property
set_value = status_add.Value
if set_property != '':
self.add_attr[set_property] = (
set_value + self.add_attr.get(set_property, 0)
)
if count == 4:
status_add = RelicSetSkill.RelicSet[str(set_id)]['4']
if status_add:
set_property = status_add.Property
set_value = status_add.Value
if set_property != '':
self.add_attr[set_property] = (
set_value + self.add_attr.get(set_property, 0)
)
# logger.info(json.dumps(self.base_attributes))
# logger.info(json.dumps(self.add_attr))

View File

@ -3,14 +3,18 @@ from pathlib import Path
from typing import Dict, List, Union from typing import Dict, List, Union
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from starrail_damage_cal.map.SR_MAP_PATH import avatarId2Name
from starrail_damage_cal.to_data import api_to_dict
from .to_data import api_to_dict
from ..utils.image.convert import convert_img
from ..utils.fonts.first_world import fw_font_28 from ..utils.fonts.first_world import fw_font_28
from ..utils.map.SR_MAP_PATH import avatarId2Name
from ..utils.map.name_covert import avatar_id_to_char_star
from ..utils.fonts.starrail_fonts import sr_font_24, sr_font_30, sr_font_58 from ..utils.fonts.starrail_fonts import sr_font_24, sr_font_30, sr_font_58
from ..utils.resource.RESOURCE_PATH import CHAR_ICON_PATH, CHAR_PREVIEW_PATH from ..utils.image.convert import convert_img
from ..utils.map.name_covert import avatar_id_to_char_star
from ..utils.resource.RESOURCE_PATH import (
CHAR_ICON_PATH,
CHAR_PREVIEW_PATH,
PLAYER_PATH,
)
half_color = (255, 255, 255, 120) half_color = (255, 255, 255, 120)
first_color = (29, 29, 29) first_color = (29, 29, 29)
@ -27,15 +31,18 @@ pic_500 = Image.open(TEXT_PATH / '500.png')
async def api_to_card(uid: str) -> Union[str, bytes]: async def api_to_card(uid: str) -> Union[str, bytes]:
char_data_list = await api_to_dict(uid) char_id_list, _ = await api_to_dict(
sr_uid=uid,
save_path=PLAYER_PATH,
)
if ( if (
not isinstance(char_data_list, str) not isinstance(char_id_list, str)
and char_data_list == [] and char_id_list == []
or isinstance(char_data_list, str) or isinstance(char_id_list, str)
): ):
return await convert_img(pic_500) return await convert_img(pic_500)
return await draw_enka_card(uid=uid, char_list=char_data_list, showfrom=1) return await draw_enka_card(uid=uid, char_list=char_id_list, showfrom=1)
async def draw_enka_card(uid: str, char_list: List, showfrom: int = 0): async def draw_enka_card(uid: str, char_list: List, showfrom: int = 0):
@ -54,9 +61,7 @@ async def draw_enka_card(uid: str, char_list: List, showfrom: int = 0):
return await convert_img(Image.new('RGBA', (0, 1), (255, 255, 255))) return await convert_img(Image.new('RGBA', (0, 1), (255, 255, 255)))
else: else:
line1 = f'UID {uid} 刷新成功' line1 = f'UID {uid} 刷新成功'
line2 = ( line2 = f'可以使用 sr查询{char_data_list[0]["avatarName"]} 查询详情角色面板'
f'可以使用 sr查询{char_data_list[0]["avatarName"]} 查询详情角色面板'
)
char_num = len(char_data_list) char_num = len(char_data_list)
if char_num <= 4: if char_num <= 4:
based_w, based_h = 1380, 926 based_w, based_h = 1380, 926

View File

@ -1,339 +0,0 @@
import json
from pathlib import Path
from typing import Dict, List, Union, Optional
from httpx import ReadTimeout
from msgspec import json as msgjson
from ..utils.error_reply import UID_HINT
from ..sruid_utils.api.mihomo import MihomoData
from ..sruid_utils.api.mihomo.models import Avatar
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH
from ..sruid_utils.api.mihomo.requests import get_char_card_info
from .cal_value import cal_relic_sub_affix, cal_relic_main_affix
from ..utils.excel.model import AvatarPromotionConfig, EquipmentPromotionConfig
from ..utils.map.SR_MAP_PATH import (
SetId2Name,
ItemId2Name,
Property2Name,
RelicId2SetId,
EquipmentID2Name,
AvatarRankSkillUp,
EquipmentID2Rarity,
rankId2Name,
skillId2Name,
avatarId2Name,
skillId2Effect,
avatarId2EnName,
avatarId2Rarity,
characterSkillTree,
skillId2AttackType,
avatarId2DamageType,
)
async def api_to_dict(
sr_uid: str, sr_data: Optional[MihomoData] = None
) -> Union[List[Dict], str]:
"""
:说明:
访问Mihomo.me API并转换为StarRailUID的数据Json
:参数:
* ``uid: str``: 玩家uid
* ``sr_data: Optional[Dict] = None``: 来自Mihomo.me的dict, 可留空
:返回:
* ``刷新完成提示语: str``: 包含刷新成功的角色列表
"""
if '未找到绑定的UID' in sr_uid:
return UID_HINT
if not sr_data:
try:
sr_data = await get_char_card_info(sr_uid)
except ReadTimeout:
return '网络不太稳定...'
except json.decoder.JSONDecodeError:
return '网络不太稳定...'
if isinstance(sr_data, str):
return []
if isinstance(sr_data, Dict):
if 'detailInfo' not in sr_data:
return '服务器正在维护或者关闭中...\n检查Mihomo.me是否可以访问\n如可以访问,尝试上报Bug!'
elif sr_data is None:
return []
PlayerDetailInfo = sr_data.detailInfo
path = PLAYER_PATH / str(sr_uid)
path.mkdir(parents=True, exist_ok=True)
with Path.open(path / f'{sr_uid!s}.json', 'wb') as file:
file.write(msgjson.format(msgjson.encode(PlayerDetailInfo), indent=4))
with Path.open(path / 'rawData.json', 'wb') as file:
file.write(msgjson.format(msgjson.encode(sr_data), indent=4))
if sr_data.detailInfo is None:
return f'SR_UID{sr_uid}刷新失败!未打开角色展柜!'
char_name_list = []
char_id_list = []
im = f'UID: {sr_uid} 的角色展柜刷新成功\n'
if PlayerDetailInfo.assistAvatarDetail:
if PlayerDetailInfo.assistAvatarDetail.avatarId not in char_id_list:
char_dict, avatarName = await get_data(
PlayerDetailInfo.assistAvatarDetail, sr_data, sr_uid
)
im += f'支援角色 {avatarName}\n'
char_name_list.append(avatarName)
char_id_list.append(PlayerDetailInfo.assistAvatarDetail.avatarId)
if PlayerDetailInfo.avatarDetailList:
im += '星海同行'
if PlayerDetailInfo.avatarDetailList is not None:
for char in PlayerDetailInfo.avatarDetailList:
if char.avatarId not in char_id_list:
_, avatarName = await get_data(char, sr_data, sr_uid)
im += f' {avatarName}'
char_name_list.append(avatarName)
char_id_list.append(char.avatarId)
if not char_name_list:
return f'UID: {sr_uid} 的角色展柜刷新失败!\n请检查UID是否正确或者角色展柜是否打开!'
return char_id_list
async def get_data(char: Avatar, sr_data: MihomoData, sr_uid: str):
PlayerDetailInfo = sr_data.detailInfo
path = PLAYER_PATH / str(sr_uid)
# 处理基本信息
char_data = {
'uid': str(sr_uid),
'nickName': PlayerDetailInfo.nickname,
'avatarId': char.avatarId,
'avatarName': avatarId2Name[str(char.avatarId)],
'avatarElement': avatarId2DamageType[str(char.avatarId)],
'avatarRarity': avatarId2Rarity[str(char.avatarId)],
'avatarPromotion': char.promotion,
'avatarLevel': char.level,
'avatarSkill': [],
'avatarExtraAbility': [],
'avatarAttributeBonus': [],
'RelicInfo': [],
}
avatarName = avatarId2Name[str(char.avatarId)]
char_data['avatarEnName'] = avatarId2EnName[str(char.avatarId)]
# 处理技能
for behavior in char.skillTreeList:
# 处理技能
if f'{char.avatarId}0' == str(behavior.pointId)[0:5]:
skill_temp = {}
skill_temp['skillId'] = char.avatarId * 100 + behavior.pointId % 10
skill_temp['skillName'] = skillId2Name[str(skill_temp['skillId'])]
skill_temp['skillEffect'] = skillId2Effect[
str(skill_temp['skillId'])
]
skill_temp['skillAttackType'] = skillId2AttackType[
str(skill_temp['skillId'])
]
skill_temp['skillLevel'] = behavior.level
char_data['avatarSkill'].append(skill_temp)
# 处理技能树中的额外能力
if f'{char.avatarId}1' == str(behavior.pointId)[0:5]:
extra_ability_temp = {}
extra_ability_temp['extraAbilityId'] = behavior.pointId
extra_ability_temp['extraAbilityLevel'] = behavior.level
char_data['avatarExtraAbility'].append(extra_ability_temp)
# 处理技能树中的属性加成
if f'{char.avatarId}2' == str(behavior.pointId)[0:5]:
attribute_bonus_temp = {}
attribute_bonus_temp['attributeBonusId'] = behavior.pointId
attribute_bonus_temp['attributeBonusLevel'] = behavior.level
status_add = characterSkillTree[str(char.avatarId)][
str(behavior.pointId)
]['levels'][behavior.level - 1]['properties']
attribute_bonus_temp['statusAdd'] = {}
if status_add:
for property_ in status_add:
attribute_bonus_temp['statusAdd']['property'] = property_[
'type'
]
attribute_bonus_temp['statusAdd']['name'] = Property2Name[
property_['type']
]
attribute_bonus_temp['statusAdd']['value'] = property_[
'value'
]
char_data['avatarAttributeBonus'].append(
attribute_bonus_temp
)
# 处理遗器
if char.relicList:
for relic in char.relicList:
relic_temp = {}
relic_temp['relicId'] = relic.tid
relic_temp['relicName'] = ItemId2Name[str(relic.tid)]
relic_temp['SetId'] = int(RelicId2SetId[str(relic.tid)])
relic_temp['SetName'] = SetId2Name[str(relic_temp['SetId'])]
relic_temp['Level'] = relic.level if relic.level else 0
relic_temp['Type'] = relic.type
relic_temp['MainAffix'] = {}
relic_temp['MainAffix']['AffixID'] = relic.mainAffixId
affix_property, value = await cal_relic_main_affix(
relic_id=relic.tid,
set_id=str(relic_temp['SetId']),
affix_id=relic.mainAffixId,
relic_type=relic.type,
relic_level=relic_temp['Level'],
)
relic_temp['MainAffix']['Property'] = affix_property
relic_temp['MainAffix']['Name'] = Property2Name[affix_property]
relic_temp['MainAffix']['Value'] = value
relic_temp['SubAffixList'] = []
if relic.subAffixList:
for sub_affix in relic.subAffixList:
sub_affix_temp = {}
sub_affix_temp['SubAffixID'] = sub_affix.affixId
sub_affix_property, value = await cal_relic_sub_affix(
relic_id=relic.tid,
affix_id=sub_affix.affixId,
cnt=sub_affix.cnt,
step=sub_affix.step if sub_affix.step else 0,
)
sub_affix_temp['Property'] = sub_affix_property
sub_affix_temp['Name'] = Property2Name[sub_affix_property]
sub_affix_temp['Cnt'] = sub_affix.cnt
sub_affix_temp['Step'] = (
sub_affix.step if sub_affix.step else 0
)
sub_affix_temp['Value'] = value
relic_temp['SubAffixList'].append(sub_affix_temp)
char_data['RelicInfo'].append(relic_temp)
# 处理命座
rank_temp = []
if char.rank and char.rank is not None:
char_data['rank'] = char.rank
for index in range(char.rank):
rankTemp = {}
rank_id = int(str(char.avatarId) + '0' + str(index + 1))
rankTemp['rankId'] = rank_id
rankTemp['rankName'] = rankId2Name[str(rank_id)]
rank_temp.append(rankTemp)
char_data['rankList'] = rank_temp
# 处理命座中的 level_up_skills
if char_data.get('rankList'):
for rank_item in char_data['rankList']:
rank_id = rank_item['rankId']
level_up_skill = AvatarRankSkillUp[str(rank_id)]
if level_up_skill:
for item in level_up_skill:
skill_id = item['id']
skill_up_num = item['num']
# 查找skill_id在不在avatarSkill中
for index, skill_item in enumerate(
char_data['avatarSkill']
):
if str(skill_id) == str(skill_item['skillId']):
char_data['avatarSkill'][index][
'skillLevel'
] += skill_up_num
break
# 处理基础属性
base_attributes = {}
avatar_promotion_base = AvatarPromotionConfig.Avatar[str(char.avatarId)][
str(char.promotion)
]
# 攻击力
base_attributes['attack'] = (
avatar_promotion_base.AttackBase.Value
+ avatar_promotion_base.AttackAdd.Value * (char.level - 1)
)
# 防御力
base_attributes['defence'] = (
avatar_promotion_base.DefenceBase.Value
+ avatar_promotion_base.DefenceAdd.Value * (char.level - 1)
)
# 血量
base_attributes['hp'] = (
avatar_promotion_base.HPBase.Value
+ avatar_promotion_base.HPAdd.Value * (char.level - 1)
)
# 速度
base_attributes['speed'] = avatar_promotion_base.SpeedBase.Value
# 暴击率
base_attributes[
'CriticalChanceBase'
] = avatar_promotion_base.CriticalChance.Value
# 暴击伤害
base_attributes[
'CriticalDamageBase'
] = avatar_promotion_base.CriticalDamage.Value
# 嘲讽
base_attributes['BaseAggro'] = avatar_promotion_base.BaseAggro.Value
char_data['baseAttributes'] = base_attributes
# 处理武器
equipment_info = {}
if char.equipment and char.equipment.tid is not None:
equipment_info['equipmentID'] = char.equipment.tid
equipment_info['equipmentName'] = EquipmentID2Name[
str(char.equipment.tid)
]
equipment_info['equipmentLevel'] = char.equipment.level
equipment_info['equipmentPromotion'] = char.equipment.promotion
equipment_info['equipmentRank'] = char.equipment.rank
equipment_info['equipmentRarity'] = EquipmentID2Rarity[
str(char.equipment.tid)
]
equipment_base_attributes = {}
equipment_promotion_base = EquipmentPromotionConfig.Equipment[
str(char.equipment.tid)
][str(equipment_info['equipmentPromotion'])]
equipment_level = char.equipment.level if char.equipment.level else 1
# 生命值
equipment_base_attributes['hp'] = (
equipment_promotion_base.BaseHP.Value
+ equipment_promotion_base.BaseHPAdd.Value * (equipment_level - 1)
)
# 攻击力
equipment_base_attributes['attack'] = (
equipment_promotion_base.BaseAttack.Value
+ equipment_promotion_base.BaseAttackAdd.Value
* (equipment_level - 1)
)
# 防御力
equipment_base_attributes['defence'] = (
equipment_promotion_base.BaseDefence.Value
+ equipment_promotion_base.BaseDefenceAdd.Value
* (equipment_level - 1)
)
equipment_info['baseAttributes'] = equipment_base_attributes
char_data['equipmentInfo'] = equipment_info
with Path.open(path / f'{avatarName}.json', 'w', encoding='UTF-8') as file:
json.dump(char_data, file, ensure_ascii=False)
return char_data, avatarName
async def api_to_data(
uid: str, mihomo_data: Optional[MihomoData] = None
) -> Union[Dict, str]:
raw_data = await api_to_dict(uid, mihomo_data)
if isinstance(raw_data, str):
return raw_data
char_name_list = []
char_name_list_str = ''
for char_data in raw_data:
char_name_list.append(char_data['avatarName'])
char_name_list_str = ','.join(char_name_list)
return f'UID{uid}刷新完成!\n本次缓存:{char_name_list_str}'

16
pdm.lock generated
View File

@ -6,7 +6,7 @@ groups = ["default"]
cross_platform = true cross_platform = true
static_urls = false static_urls = false
lock_version = "4.3" lock_version = "4.3"
content_hash = "sha256:4b46a9f0d8d192c3cdb2b0caa6e5bc8783bbd61c95254ec959fecc29e73fb2d6" content_hash = "sha256:95f715290809f5d3a83e79c40bd55087875dd058f16a7da9771c275752176f37"
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
@ -627,6 +627,20 @@ files = [
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
] ]
[[package]]
name = "starrail-damage-cal"
version = "1.0.0"
requires_python = ">=3.8"
summary = "For StarRail Role Damage Cal"
dependencies = [
"httpx>=0.25.0",
"msgspec>=0.18.4",
]
files = [
{file = "starrail_damage_cal-1.0.0-py3-none-any.whl", hash = "sha256:95c1757accd60bc9eda97d51399b9f26fa39ae112927c94f68da385f88defa17"},
{file = "starrail_damage_cal-1.0.0.tar.gz", hash = "sha256:ff4287c56304e996981b851e9bfe47bc898feead5511c3b831dd7e099516cef4"},
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.8.0" version = "4.8.0"

View File

@ -118,6 +118,7 @@ dependencies = [
"aiofiles>=23.2.1", "aiofiles>=23.2.1",
"aiohttp>=3.8.6", "aiohttp>=3.8.6",
"qrcode[pil]>=7.4.2", "qrcode[pil]>=7.4.2",
"starrail-damage-cal>=1.0.0",
] ]
requires-python = ">=3.8.1,<4.0" requires-python = ">=3.8.1,<4.0"
readme = "README.md" readme = "README.md"

View File

@ -405,6 +405,9 @@ sniffio==1.3.0 \
soupsieve==2.5 \ soupsieve==2.5 \
--hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \ --hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \
--hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7 --hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7
starrail-damage-cal==1.0.0 \
--hash=sha256:95c1757accd60bc9eda97d51399b9f26fa39ae112927c94f68da385f88defa17 \
--hash=sha256:ff4287c56304e996981b851e9bfe47bc898feead5511c3b831dd7e099516cef4
typing-extensions==4.8.0 \ typing-extensions==4.8.0 \
--hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \
--hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef