Merge pull request !2 from 西瓜/dev
This commit is contained in:
西瓜
2026-04-21 04:08:20 +00:00
committed by Gitee
123 changed files with 13543 additions and 2639 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
[submodule "backend/openapi"]
path = backend/openapi
url = https://gitee.com/dnftools/dcalcopenapi.git
url = ../dcalcopenapi.git
[submodule "images"]
path = images
url = https://gitee.com/dnftools/dcalcimages.git
url = ../dcalcimages.git
+49 -29
View File
@@ -2,11 +2,12 @@ import re
from api.core.Auth import createToken
from api.core.Gzip import register_gzip_request
import json
from fastapi import APIRouter,Request, Depends, Body
from fastapi import APIRouter, Request, Depends, Body
from typing import Annotated
from api.core.Response import Return, response
from api.core.Redis import get_redis_info
from api.dp import RedisDep,AltersDep
from api.dp import RedisDep, AltersDep
from core.basic.equipment import get_equipment
from core.character.adventure import get_adv_list
from core.basic.character import createCharacter
from core.open.helper import get_user_info_by_id
@@ -17,6 +18,7 @@ router = APIRouter()
register_gzip_request(router)
def replace_placeholders(template):
# 匹配 <int>、<float>、<float1>、<float2> 等
pattern = re.compile(r'<(int|float\d*)>')
@@ -30,44 +32,60 @@ def replace_placeholders(template):
return pattern.sub(repl, template)
@router.get('/adventure')
async def get_adventure_info(request: Request,redis:RedisDep):
async def get_adventure_info(request: Request, redis: RedisDep):
adventure_info = get_redis_info(redis, 'dcalc:adventure', get_adv_list)
return response(data=adventure_info)
@router.get("/token/get/{alter}")
async def getToken(redis:RedisDep,
alter: str, request: Request, version: str = None, equVersion: str = "0"
):
@router.get('/token/get/{alter}')
async def getToken(redis: RedisDep, alter: str, request: Request, version: str = None, equVersion: str = '0'):
token = createToken(alter, equVersion, redis)
return response(data=token)
@router.get("/character")
async def get_character_info(
request: Request, state: AltersDep ,redis:RedisDep
):
@router.get('/character')
async def get_character_info(request: Request, state: AltersDep, redis: RedisDep):
character = createCharacter(state.alter, state.equVersion)
def get_character():
return character.getInfo()
info = get_redis_info(redis, f"dcalc:character:{state.alter}:{state.equVersion}", get_character)
info = get_redis_info(redis, f'dcalc:character:{state.alter}:{state.equVersion}', get_character)
# info = character.getInfo()
return response(data=info)
@router.get("/skill/{skillId}/{level}")
async def get_skill_info(
state: AltersDep, skillId: str, level: int, redis: RedisDep
):
@router.get('/info/oath/{suitId}/{skillId}')
async def get_oath_info(state: AltersDep, suitId: str, skillId: str, rarity: str, redis: RedisDep):
def get_info():
skill = None
version = state.equVersion
equ = get_equipment(version)
oath = next((x for x in equ.oath_suits if x.suitId == suitId and x.rarity == rarity), None)
if oath is not None:
oathSkill = next((x for x in oath.skills if x.skillId == skillId), None)
if oathSkill is not None:
skill = {**oathSkill.__dict__, 'point': oath.points, 'imageUrl': oath.imageUrl}
return skill
info = get_redis_info(redis, f'dcalc:oath:{rarity}:{skillId}', get_info)
return response(data=info)
@router.get('/skill/{skillId}/{level}')
async def get_skill_info(state: AltersDep, skillId: str, level: int, redis: RedisDep):
"""
获取技能信息
"""
alter = state.alter
jobId = alter.split(".")[1]
jobGrowId = alter.split(".")[2]
jobId = alter.split('.')[1]
jobGrowId = alter.split('.')[2]
skill_info = {}
key = f'openapi:{jobId}:{jobGrowId}:{skillId}'
try:
def get_skill_info():
# This function should retrieve the skill info based on job and jobGrow
# For now, we return a placeholder dictionary
@@ -97,25 +115,27 @@ async def get_skill_info(
return response(data=skill_info)
@router.get("/dnfhelper/{uid}/")
@router.get('/dnfhelper/{uid}/')
async def get_info_by_dnfhelper(uid: str, state: AltersDep, redis: RedisDep):
try:
result = await get_user_info_by_id(uid,state)
result = await get_user_info_by_id(uid, state)
return response(data=result)
except Exception as e:
if isinstance(e, ResponseException):
return response(code=500, message=str(e), data=None)
return response(code=500, message="Failed to fetch DNFHelper info", data=None)
print(e)
return response(code=500, message='Failed to fetch DNFHelper info', data=None)
@router.post("/skillTree/")
async def get_skill_by_code(state: AltersDep, code = Body(None)):
@router.post('/skillTree/')
async def get_skill_by_code(state: AltersDep, code=Body(None)):
try:
skills = get_skill_tree_info(code.get("code", ""))
if skills.get("job",{}).get("class","") != state.alter:
return response(code=500, message="技能加点代码职业与登录职业不匹配", data=None)
skills = get_skill_tree_info(code.get('code', ''))
if skills.get('job', {}).get('class', '') != state.alter:
return response(code=500, message='技能加点代码职业与登录职业不匹配', data=None)
return response(data=skills)
except Exception as e:
print(f"Error fetching skill tree info: {e}")
print(f'Error fetching skill tree info: {e}')
if isinstance(e, ResponseException):
return response(code=500, message=str(e), data=None)
return response(code=500, message="解析技能加点失败", data=None)
return response(code=500, message='解析技能加点失败', data=None)
+3 -3
View File
@@ -3,6 +3,6 @@ MCPPORT=47174
DEBUG_MODE=True
WORKERS=1
REDIS_URL=redis://localhost:6379/1
COLG_TOKEN="Bearer "
HELPER_TOKEN=""
HELPER_ID=""
COLG_TOKEN="Bearer 7B8B817D9BC838B813A1E6B1A0CAFD5F"
HELPER_TOKEN="ESc6Z2kW"
HELPER_ID="706194702"
+12 -3
View File
@@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from typing import Any, Literal, Mapping
class CharacterProperty(ABC):
"""
抽象类,用于定义Character的接口和属性
@@ -152,7 +153,8 @@ class CharacterProperty(ABC):
攻击强化P=0,
增益量=0,
增益量P=0,
**kwargs: Mapping[Literal[
**kwargs: Mapping[
Literal[
'STR',
'INT',
'AtkP',
@@ -187,7 +189,9 @@ class CharacterProperty(ABC):
'Spirit',
'Vitality',
'EquEffectRatio',
], Any],
],
Any,
],
) -> None:
"""设置角色属性"""
pass
@@ -198,7 +202,7 @@ class CharacterProperty(ABC):
pass
@abstractmethod
def AddSkillLv(self, min: int, max: int, lv: int, type=-1,exceptSkills:list[str]=[]) -> None:
def AddSkillLv(self, min: int, max: int, lv: int, type=-1, exceptSkills: list[str] = []) -> None:
"""增加技能等级"""
pass
@@ -236,3 +240,8 @@ class CharacterProperty(ABC):
def calc(self, setInfo: dict[str, dict]) -> dict:
"""计算角色属性"""
pass
@abstractmethod
def GetReinforceSum(self) -> list[int]:
"""获取强化总和"""
pass
+43
View File
@@ -0,0 +1,43 @@
"""
core.basic.character 包
- jade.py : Jade(辟邪玉)数据类
- base.py : CharacterBase — 属性声明、__init__、基础状态设置、技能操作
- calc_equip.py : CalcEquipMixin — 套装/装备/时装/基础强化/辟邪玉/杂项计算 + calc 主入口
- calc_carry.py : CalcCarryMixin — 输出角色伤害系数与技能伤害计算
- calc_buffer.py : CalcBufferMixin — 辅助角色 Buff 技能计算
- info.py : CharacterInfoMixin — 前端展示信息查询
"""
import importlib
from .jade import Jade
from .calc_equip import CalcEquipMixin
from .calc_carry import CalcCarryMixin
from .calc_buffer import CalcBufferMixin
from .info import CharacterInfoMixin
class Character(CalcEquipMixin, CalcCarryMixin, CalcBufferMixin, CharacterInfoMixin):
"""
角色类,通过多重继承组合各模块能力。
MRO(方法解析顺序):
Character → CalcEquipMixin → CalcCarryMixin → CalcBufferMixin
→ CharacterInfoMixin → CharacterBase → CharacterProperty
"""
def createCharacter(alter: str, equVersion: str = '0') -> 'Character':
character: Character = None
module_name = 'core.character.' + alter
if equVersion is None or equVersion == '':
equVersion = '0'
try:
character = importlib.import_module(module_name).classChange(equVersion)
except Exception as ex:
print(ex)
print('create character ' + module_name + ' error.')
return character
__all__ = ['Character', 'Jade', 'createCharacter']
+378
View File
@@ -0,0 +1,378 @@
from collections.abc import Mapping
import sys
from typing import Any, Literal
from core.abstract.character import CharacterProperty
from core.basic.skill import Skill
from core.basic.equipment import EquEffect, OathSuit, OathSuitSkill, Suit
from ..formula import 获取基础属性, 获取唤醒属性
from ..roleinfo import CharacterEmblemInfo, CharacterEquipInfo, get_key_by_value, CharacterOathInfo
from .jade import Jade
class CharacterBase(CharacterProperty):
# region 额外类属性(CharacterProperty 中未声明的)
BindAwake: int
"""绑定觉醒等级"""
skills: list['Skill'] = []
skills_dict: dict[str, 'Skill'] = {}
equVersion: str = '0'
"""装备版本"""
charEquipInfo: dict[str, 'CharacterEquipInfo']
"""装备打造信息"""
charEmblemInfo: list['CharacterEmblemInfo']
"""徽章打造信息"""
charOathInfo: list['CharacterOathInfo']
"""誓约打造信息"""
charOathSkillId: int
"""誓约技能ID"""
suitInfo: list['Suit']
"""套装信息"""
oathSuitInfo: list['OathSuit']
"""誓约套装信息"""
oathSkillInfo: list['OathSuitSkill']
"""誓约技能信息"""
equ_effect: list['EquEffect'] = []
"""装备效果列表"""
equ_options: dict[str, int] = {}
"""装备选项列表"""
EquEffectRatio = 1.0
"""装备效果倍率"""
max_point = 0
"""套装效果点数"""
buffer: bool = False
# region 职业属性
实际名称: str = ''
名称: str = ''
角色: str = ''
角色类型: str = ''
职业: str = ''
武器选项: list[str] = []
副武器选项: list[str] = []
输出类型选项: list[str] = []
防具精通属性: list[str] = []
输出类型: str = '物理百分比'
# endregion
# endregion
# region 跨 Mixin 方法前向声明(供静态类型检查使用,由对应 Mixin 实现)
def load_skills(self) -> None:
"""加载技能(由 CalcEquipMixin 实现)"""
def calc_carry_skills(self, DSB: bool, BUFF: bool) -> list:
"""计算输出技能(由 CalcCarryMixin 实现)"""
return []
def get_carry_info(self) -> list:
"""获取输出信息(由 CalcCarryMixin 实现)"""
return []
def calc_buffer_skills(self) -> list:
"""计算辅助技能(由 CalcBufferMixin 实现)"""
return []
def get_buffer_info(self) -> list:
"""获取辅助信息(由 CalcBufferMixin 实现)"""
return []
# endregion
def __init__(self, equVersion, moduleName) -> None:
self.equVersion = equVersion
self.moduleName = moduleName
# 工会四维
self.STR = 0 + 120
self.INT = 0 + 120
self.Spirit = 0 + 120
self.Vitality = 0 + 120
# 唤醒的情况下的基础数据
self.AtkP = 0 + 65
self.AtkM = 0 + 65
self.AtkI = 0 + 65
self.PAtkP = 1.0
self.PAtkM = 1.0
self.PAtkI = 1.0
self.ElementA = {
'火': False,
'冰': False,
'光': False,
'暗': False,
}
self.ElementDB = {
'火': 13,
'冰': 13,
'光': 13,
'暗': 13,
}
self.ElementR = {
'火': 0,
'冰': 0,
'光': 0,
'暗': 0,
}
self.Abnormal = {
'出血': False,
'灼伤': False,
'感电': False,
'中毒': False,
}
self.CriticalM = 0
self.CriticalP = 0
self.SkillAttack = 1
self.SpeedA = 0
self.SpeedM = 0
self.SpeedR = 0
self.HitP = 0
self.Hit = 0
self.MonsterInfo = {
'DefenseP': 0,
'DefenseM': 0,
'火抗': 0,
'冰抗': 0,
'光抗': 0,
'暗抗': 0,
}
self.Buffer = 0
self.BufferP = 1.0
self.Attack = 0
self.AttackP = 1.0
self.ElementIncrease = 1.0
self.equs = {}
self.jade_effect = Jade()
self.equ_effect = []
self.equ_options = {}
self.max_point = 0
self.skills = []
self.skills_dict = {}
self.load_skills()
# region 角色属性设置
def SetBaseStatus(self) -> None:
"""设角色基础属性"""
temp = 获取基础属性(self.角色, self.职业)
唤醒 = 获取唤醒属性()
for index, i in enumerate([get_key_by_value(i) for i in ['力量', '体力', '智力', '精神']]):
setattr(self, i, getattr(self, i) + temp[index] + 唤醒)
def SetStatus(
self,
力量=0,
智力=0,
力智=0,
体力=0,
精神=0,
体精=0,
四维=0,
物攻=0,
魔攻=0,
独立=0,
三攻=0,
技攻=0,
全属强=0,
全属抗=0,
移动速度=0,
攻击速度=0,
施放速度=0,
三速=0,
命中=0,
命中率=0,
物暴=0,
魔暴=0,
攻击强化=0,
攻击强化P=0,
增益量=0,
增益量P=0,
**kwargs: Mapping[
Literal[
'STR',
'INT',
'AtkP',
'AtkM',
'AtkI',
'PAtkP',
'PAtkM',
'PAtkI',
'PATKIPDamage',
'PDamageC',
'PSTR',
'PINT',
'PDamageB',
'ElementA',
'ElementDB',
'ElementR',
'CriticalMP',
'CriticalM',
'CriticalPP',
'CriticalP',
'EquipmentSkillAttack',
'SkillAttack',
'SpeedA',
'SpeedM',
'SpeedR',
'HitP',
'Hit',
'Attack',
'AttackP',
'Buffer',
'BufferP',
'Spirit',
'Vitality',
'EquEffectRatio',
'火强',
'冰强',
'光强',
'暗强',
'火属性抗性',
'冰属性抗性',
'光属性抗性',
'暗属性抗性',
'火属性攻击',
'冰属性攻击',
'光属性攻击',
'暗属性攻击',
],
Any,
],
) -> None:
"""设置角色属性"""
# if kwargs.get('SkillAttack', 0) != 0:
# frame = sys._getframe(1) # 1 表示上一级调用帧
# caller_name = frame.f_code.co_name
# print(caller_name, kwargs.get('SkillAttack', 0))
self.STR += 力量 + kwargs.get('STR', 0) + 四维 + 力智
self.INT += 智力 + kwargs.get('INT', 0) + 四维 + 力智
self.Spirit += 精神 + kwargs.get('Spirit', 0) + 四维 + 体精
self.Vitality += 体力 + kwargs.get('Vitality', 0) + 四维 + 体精
self.AtkP += 物攻 + kwargs.get('AtkP', 0) + 三攻
self.AtkM += 魔攻 + kwargs.get('AtkM', 0) + 三攻
self.AtkI += 独立 + kwargs.get('AtkI', 0) + 三攻
self.PAtkP *= kwargs.get('PAtkP', 1.0)
self.PAtkM *= kwargs.get('PAtkM', 1.0)
self.PAtkI *= kwargs.get('PAtkI', 1.0)
for elem in ['火', '冰', '光', '暗']:
self.ElementA[elem] = kwargs.get(f'{elem}属性攻击', False) or kwargs.get('ElementA', {}).get(elem, False) or self.ElementA[elem]
self.ElementDB[elem] += kwargs.get(f'{elem}强', 0) + kwargs.get('ElementDB', {}).get(elem, 0) + 全属强
self.ElementR[elem] += kwargs.get(f'{elem}抗', 0) + kwargs.get('ElementR', {}).get(elem, 0) + 全属抗
self.CriticalM += kwargs.get('CriticalM', 0) + 魔暴
self.CriticalP += kwargs.get('CriticalP', 0) + 物暴
self.SkillAttack *= (1 + 技攻) * (1 + kwargs.get('SkillAttack', 0))
self.SpeedA += 攻击速度 + kwargs.get('SpeedA', 0) + 三速
self.SpeedM += 移动速度 + kwargs.get('SpeedM', 0) + 三速
self.SpeedR += 施放速度 + kwargs.get('SpeedR', 0) + 三速
self.HitP += 命中率 + kwargs.get('HitP', 0)
self.Hit += 命中 + kwargs.get('Hit', 0)
self.Attack += 攻击强化 + kwargs.get('Attack', 0)
self.AttackP += 攻击强化P + kwargs.get('AttackP', 0.0)
self.Buffer += 增益量 + kwargs.get('Buffer', 0)
self.BufferP += 增益量P + kwargs.get('BufferP', 0)
self.EquEffectRatio += kwargs.get('EquEffectRatio', 0.0)
def AddElementDB(self, element: str, value: float, type: int = 0) -> None:
"""
增加属性强化
type: 0 站街 1 进图
"""
if element not in self.ElementDB:
raise ValueError(f'Invalid element: {element}')
if type == 0:
value = value * (1 + self.jade_effect.ElementIncrease)
if type == 1:
value = int(value * (1 + self.jade_effect.ElementIncrease))
self.ElementDB[element] += value
def AddSkillLv(self, min: int, max: int, lv: int, type=-1, exceptSkills: list[str] = []) -> None:
"""
增加技能等级
type: -1 全部, 0 被动, 1 主动
"""
skillType = 'all' if type == -1 else ('active' if type == 1 else 'passive')
for skill in self.skills:
if min <= skill.learnLv <= max:
if (skillType == 'all' or skill.type == skillType) and skill.lv > 0 and skill.name not in exceptSkills:
skill.lv += lv
def SetSkillCD(self, min=1, max=100, cd=0, exclude=[50, 85, 100]) -> None:
"""设置技能CD"""
for skill in self.skills:
if min <= skill.learnLv <= max and (skill.type == 'active' or skill.damage) and skill.learnLv not in exclude:
skill.cdReduce *= 1 - cd
def SetSkillCDRecover(self, min=1, max=100, cd=0, exclude=[50, 85, 100]) -> None:
"""设置技能CD恢复"""
for skill in self.skills:
if min <= skill.learnLv <= max and (skill.type == 'active' or skill.damage) and skill.learnLv not in exclude:
skill.cdRecover += cd
def SetSkillRation(self, min=1, max=100, ratio=1, type=-1) -> None:
"""设置技能倍率
0 修改技能面板 1 不修改技能面板
type: -1 全部, 0 被动, 1 主动
"""
for skill in self.skills:
if min <= skill.learnLv <= max and skill.damage and skill.type == 'active':
if type == 0:
skill.skillRation *= 1 + ratio
else:
skill.skillDamage *= 1 + ratio
# endregion
# region 技能查询
def GetSkillByName(self, name) -> 'Skill':
"""通过技能名获取技能"""
return self.skills_dict.get(name, None)
def GetSkillByID(self, id: str) -> 'Skill':
"""通过技能ID获取技能"""
skill = list(filter(lambda x: str(x.id) == id, self.skills))
return skill[0] if len(skill) > 0 else None
def GetSkillByUUID(self, uuid: str) -> 'Skill':
"""通过技能UUID获取技能"""
skill = list(filter(lambda x: str(x.uuid) == uuid, self.skills))
return skill[0] if len(skill) > 0 else None
def GetSkillNames(self, type: Literal['active', 'passive', 'all'] = 'all', damage: Literal[True, False, 'all'] = 'all') -> list[str]:
"""获取技能名字"""
return [skill.name for skill in self.skills if (type == 'all' or skill.type == type) and (damage == 'all' or skill.damage == damage)]
def GetWeaponType(self) -> tuple[str, str]:
"""获取武器类型"""
weapon = self.charEquipInfo['武器'].equInfo
if weapon is None:
return None, None
else:
return weapon.itemDetailType, weapon.categorize
# endregion
def GetReinforceSum(self) -> list[int]:
"""获取强化总和"""
reinforce_0 = 0
"""防具"""
reinforce_1 = 0
"""首饰"""
reinforce_2 = 0
"""特殊装备"""
reinforce_3 = 0
"""武器"""
for part in self.charEquipInfo.keys():
if self.charEquipInfo[part] is None:
continue
num = self.charEquipInfo[part].reinforce
# if part == '武器':
# reinforce_3 += num
if part in ['上衣', '头肩', '下装', '腰带', '鞋']:
reinforce_0 += num
if part in ['手镯', '项链', '戒指']:
reinforce_1 += num
if part in ['耳环', '辅助装备', '魔法石']:
reinforce_2 += num
reinforce = reinforce_0 + reinforce_1 + reinforce_2 + reinforce_3
return [reinforce, reinforce_0, reinforce_1, reinforce_2]
@@ -0,0 +1,64 @@
from core.basic.property import CharacterInfo
from ..roleinfo import get_key_by_value
from .base import CharacterBase
class CalcBufferMixin(CharacterBase):
"""辅助角色 Buff 技能计算相关方法"""
# region 辅助计算项
def calc_buffer_skills(self):
skillInfos = []
# 适用属性
main = getattr(self, get_key_by_value(self.适用属性))
# 增益量
buffer_power = self.Buffer * self.BufferP
# buff系数
buff_ratio_new = (main / 2993 + 1) * (buffer_power / 4800 + 1) if buffer_power > 0 else 0
buff_ratio_old = main / 2993 + 1
# 觉醒系数
awake_ratio_new = ((main + 7500) / 7500 + 1) * ((buffer_power + 68000) / 8500 + 1) if buffer_power > 0 else 0
awake_ratio_old = main / 7500 + 1
for i in self.skills:
if i.lv > 0 and i.hasUP and i.up is not None and i.up > 0:
self.SetStatus(四维=40)
if i.buffer and i.lv > 0:
info = i.skillInfo()
value = info[0]
if i.buffType == 'buff' or i.buffType == 'buffSub':
value1 = (info[1][0] + info[1][2]) * info[1][1] * buff_ratio_old + info[1][0] * buff_ratio_new
value2 = (info[2][0] + info[2][2]) * info[2][1] * buff_ratio_old + info[2][0] * buff_ratio_new
elif i.buffType == 'awake' or i.buffType == 'awakeSub':
value1 = (info[1][0] + info[1][2]) * info[1][1] * awake_ratio_old + info[1][0] * awake_ratio_new
value2 = (info[2][0] + info[2][2]) * info[2][1] * awake_ratio_old + info[2][0] * awake_ratio_new
else:
value1 = (info[1][0] + info[1][2]) * info[1][1]
value2 = (info[2][0] + info[2][2]) * info[2][1]
value3 = info[3]
value4 = info[4]
if i.buffType == 'awake' and self.bindAwake == 50:
pass
else:
skillInfos.append(
{
'name': i.name,
'cd': value4,
'icon': i.icon,
'lv': i.lv,
'buff': [value, value1, value2, value3],
'type': i.type,
}
)
return skillInfos
def get_buffer_info(self):
attrs = [get_key_by_value(self.适用属性), 'Buffer', 'BufferP']
info = []
for attr in attrs:
temp = getattr(CharacterInfo, attr)
value = getattr(self, attr)
info.append({'name': temp.name, 'value': temp.value(value)})
return info
# endregion
+103
View File
@@ -0,0 +1,103 @@
from ..formula import 获取基础属性
from core.basic.property import CharacterInfo
from .base import CharacterBase
class CalcCarryMixin(CharacterBase):
"""输出角色技能伤害计算相关方法"""
# region 输出计算项
def calc_damage_ration(self, DSB: bool, BUFF: bool):
"""计算属性系数"""
attrs = []
if self.输出类型 == '物理百分比':
attrs.extend(['STR', 'AtkP', 'PAtkP'])
if self.输出类型 == '魔法百分比':
attrs.extend(['INT', 'AtkM', 'PAtkM'])
if self.输出类型 == '物理固伤':
attrs.extend(['STR', 'AtkI', 'PAtkI'])
if self.输出类型 == '魔法固伤':
attrs.extend(['INT', 'AtkI', 'PAtkI'])
# 力智系数
value0_1 = getattr(self, attrs[0])
value0_2 = 获取基础属性(self.角色, self.职业)[0] if attrs[0] == 'STR' else 获取基础属性(self.角色, self.职业)[1]
# 系统奶及奶系增幅
value = value0_1 + ((value0_1 - value0_2) * 3.08 + 2886) * (1 if DSB else 0) + (170000 + 300000) * (1 if BUFF else 0)
ratio_0: float = value / 250 + 1
# 物理/魔法/独立攻击力
ratio_1: float = getattr(self, attrs[1]) + 30000 * (1 if BUFF else 0)
# 技能 物理/魔法/独立攻击力%
ratio_2: float = getattr(self, attrs[2])
# 属强系数
ratio_3 = max(self.ElementDB.values()) * 0.0045 + 1.05
# 暴击系数
ratio_4 = 1.5
# BUFF系数
ratio_5 = self.buff
# 技攻系数
ratio_6 = self.SkillAttack * (self.jade_effect.SkillAttack + 1)
# 攻击强化
ratio_7 = 1 + self.Attack / 100 * (self.AttackP + self.jade_effect.AttackP)
# 防御系数,暂定145沙袋防御
monster_defense = 81417275817
ratio_8 = 1 - monster_defense / (monster_defense + 200 * 100)
# 杂项
ratio_9 = 1.0 * self.ElementIncrease
return (ratio_0, ratio_1, ratio_2, ratio_3, ratio_4, ratio_5, ratio_6, ratio_7, ratio_8, ratio_9)
def calc_carry_skills(self, DSB: bool, BUFF: bool):
skillInfos = []
for i in self.skills:
if i.damage and i.lv > 0:
for mode in i.mode:
temp = i.skillInfo(mode)
if temp[0] > 0:
skillInfos.append(
{
'name': i.name,
'icon': i.icon,
'lv': i.lv,
'data': temp[0],
'ratio': temp[1],
'cd': temp[2],
'mode': mode,
'learnLv': i.learnLv,
'type': i.type,
}
)
ratios = self.calc_damage_ration(DSB, BUFF)
ratio_char_skill = ratios[0] * ratios[1] * ratios[2] * ratios[3] * ratios[4] * ratios[5] * ratios[6] * ratios[7] * ratios[8] * ratios[9] / 1000
ratuio_equ_skill = ratios[0] * ratios[1] * ratios[3] * ratios[4] * ratios[6] * ratios[7] * ratios[8] * ratios[9] / 1000
for i in skillInfos:
i['damage'] = ratio_char_skill * i['data'] * i['ratio'] / 100
for i in self.equ_effect:
skillInfos.append({'name': i.name, 'icon': i.icon, 'lv': 0, 'data': i.data, 'ratio': 10.0, 'cd': i.cd, 'damage': ratuio_equ_skill * i.data * 10 * self.EquEffectRatio / 100, 'mode': ''})
return skillInfos
def get_carry_info(self):
attrs = []
if self.输出类型 == '物理百分比':
attrs.extend(['STR', 'AtkP'])
if self.输出类型 == '魔法百分比':
attrs.extend(['INT', 'AtkM'])
if self.输出类型 == '物理固伤':
attrs.extend(['STR', 'AtkI'])
if self.输出类型 == '魔法固伤':
attrs.extend(['INT', 'AtkI'])
info = []
attrs.extend(['Attack', 'AttackP', 'SkillAttack', 'EquEffectRatio', 'ElementDB'])
for attr in attrs:
temp = getattr(CharacterInfo, attr)
if attr.startswith('Atk'):
info.append({'name': temp.name, 'value': temp.value(getattr(self, attr) * getattr(self, f'P{attr}'))})
else:
value = getattr(self, attr)
if attr == 'SkillAttack':
value *= self.jade_effect.SkillAttack + 1
if attr == 'AttackP':
value += self.jade_effect.AttackP
info.append({'name': temp.name, 'value': temp.value(value)})
return info
# endregion
+379
View File
@@ -0,0 +1,379 @@
import sys
from uuid import uuid1
from core.basic.equipment import OathSuit, Suit, get_equipment
from ..formula import 增幅计算, 强化技攻, 武器强化计算, 精通计算, 耳环计算, 左右计算, 锻造计算
from ..roleinfo import CharacterEmblemInfo, CharacterEquipInfo, CharacterOathInfo, get_key_by_value
from .base import CharacterBase
class CalcEquipMixin(CharacterBase):
"""装备计算、角色初始化相关方法"""
# region 打造信息导入
def SetDetail(self, info: dict[str, dict]) -> None:
"""打造信息导入"""
self.charEquipInfo = {}
self.charOathInfo = []
self.charEmblemInfo = []
self.bindAwake = info.get('bindAwake', 50)
if info.get('emblems', None) is None:
info['emblems'] = {}
if info.get('oaths', None) is None:
info['oaths'] = {}
for key in info['equips']:
self.charEquipInfo[key] = CharacterEquipInfo(info['equips'][key], self.equVersion, key)
for key in info['oaths']:
# 导入誓约打造信息、誓约信息
self.charOathInfo.append(CharacterOathInfo(info['oaths'][key], self.equVersion, key))
try:
# 异常处理 彩色等级自动取最小值
minEmblemLv = min([info['emblems'][key]['lv'] for key in ['红色', '蓝色', '绿色', '黄色']], default=1)
info['emblems']['彩色']['lv'] = minEmblemLv
for key in info['emblems']:
# 导入徽章打造信息、徽章信息
info['emblems'][key]['part'] = key
self.charEmblemInfo.append(CharacterEmblemInfo(info['emblems'][key]))
pass
except Exception as e:
pass
self.charOathSkillId = info.get('oathSkill', 1)
def calc_init(self, setInfo: dict[str, dict]):
self.equ_options = setInfo.get('options', {})
self.setInfo = setInfo
self.SetDetail(setInfo)
self.SetSkills(setInfo.get('skills', {}))
def SetSkills(self, skills: dict[str, dict]):
"""设置技能等级"""
for key in skills:
skill = self.GetSkillByID(key)
if skill is not None:
if skill.hasUP:
skill.up = skills[key].get('up', 0)
if skill.hasVP:
skill.vp = skills[key].get('vp', 0)
skill.lv += skills[key].get('lv', 0)
def load_skills(self) -> None:
"""加载技能"""
module = sys.modules[self.moduleName]
for name in dir(module):
memberClass = getattr(module, name)
if isinstance(memberClass, type) and memberClass.__name__.startswith('Skill'):
skill = memberClass(char=self)
self.skills_dict[skill.name] = skill
self.skills.append(skill)
# endregion
# region 套装 / 装备 / 时装 / 基础属性 / 辟邪玉 / 杂项
def calc_suits(self):
"""套装效果统计"""
suitInfo = {}
oathSuitInfo = {}
equs = get_equipment(self.equVersion)
for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values()):
partEqu = item.equInfo
# partFusion = item.fusionInfo
if partEqu is not None:
for suit in partEqu.suit:
if suitInfo.get(suit, None) is None:
suitInfo[suit] = {'point': 0, 'count': 0}
if partEqu.Point == 0:
suitInfo[suit]['count'] += 1
else:
suitInfo[suit]['point'] += partEqu.Point
for item in self.charOathInfo:
partOath = item.oathInfo
if partOath is not None:
for suit in partOath.suit:
if oathSuitInfo.get(suit, None) is None:
oathSuitInfo[suit] = {'point': 0, 'count': 0}
if partOath.Point == 0:
oathSuitInfo[suit]['count'] += 1
else:
oathSuitInfo[suit]['point'] += partOath.Point
max_point_suit = max((suit for suit in suitInfo if suitInfo[suit]['point'] > 0), key=lambda suit: suitInfo[suit]['point'], default=None)
max_point_oath_suit = max((suit for suit in oathSuitInfo if oathSuitInfo[suit]['point'] > 0), key=lambda suit: oathSuitInfo[suit]['point'], default=None)
zero_point_suits = [suit for suit in suitInfo if suitInfo[suit]['point'] == 0]
# 套装没有到太初的时候,优先把誓约套装的点数加到装备套装上,以达到更高的套装等级
if suitInfo is not None and 0 < suitInfo[max_point_suit]['point'] < 2550 and oathSuitInfo.get('max_point_oath_suit', None) is not None:
suitInfo[max_point_suit]['point'] += min(2550 - suitInfo[max_point_suit]['point'], oathSuitInfo[max_point_oath_suit]['point'])
oathSuitInfo[max_point_oath_suit]['point'] -= min(2550 - suitInfo[max_point_suit]['point'], oathSuitInfo[max_point_oath_suit]['point'])
pass
suits = zero_point_suits + ([max_point_suit] if max_point_suit is not None else [])
suitList: list[Suit] = []
oathSuitList: list[OathSuit] = []
res = []
for suit in suits:
if suitInfo[suit]['point'] == 0:
suitList += equs.get_suit_info(suit, 0, suitInfo[suit]['count'])
else:
suitList += equs.get_suit_info(suit, suitInfo[suit]['point'], 0)
if len(suitList) == 0:
continue
self.suitInfo = suitList
temp = suitList[-1]
res.append(
{
'id': temp.id,
'name': temp.name,
'rarity': temp.rarity,
'point': suitInfo[suit]['point'],
'count': suitInfo[suit]['count'],
'level': temp.level,
'imageUrl': temp.imageUrl,
'value': temp.value,
}
)
for suit in oathSuitInfo:
if oathSuitInfo[suit]['point'] == 0:
oathSuitList += equs.get_oath_suit_info(suit, 0, oathSuitInfo[suit]['count'])
else:
oathSuitList += equs.get_oath_suit_info(suit, oathSuitInfo[suit]['point'], 0)
if len(oathSuitList) == 0:
continue
for suit in suitList:
i = suit.id
func = equs.funs.execture(f'suit_{i}')
suit = next((x for x in equs.suits if str(x.id) == str(i)), None)
# 誓约和套装进行匹配
# 如果存在誓约和套装是同一个套装
# 则直接不执行原套装效果,执行誓约套装增强的效果
oathEffect = next((x for x in oathSuitList if x.suitId == suit.suitId), None)
if suit is not None:
filtered_dict = {k: v for k, v in suit.__dict__.items() if k[0].isupper()}
self.SetStatus(**filtered_dict)
if func is not None and oathEffect is None:
print(f'套装 {suit.name} 生效')
func(self)
else:
# 当存在套装和誓约不匹配的时候,誓约技能只能是1号位
self.charOathSkillId = 1
oathSkillEffect = []
self.max_point = max([i['point'] for i in res], default=0)
self.oathSuitInfo = []
self.oathSkillInfo = []
# 当套装点数达到太初时,誓约技能生效
if self.max_point >= 2550:
for oathSuit in oathSuitList:
skill = next((x for x in oathSuit.skills if x.skillId == self.charOathSkillId), None)
# 誓约套装效果生效
filtered_dict = {k: v for k, v in oathSuit.__dict__.items() if k[0].isupper()}
self.SetStatus(**filtered_dict)
if skill is not None:
oathSkillEffect.append(skill)
# 誓约技能效果生效
filtered_dict = {k: v for k, v in skill.__dict__.items() if k[0].isupper()}
self.SetStatus(**filtered_dict)
self.oathSkillInfo.append(skill)
self.oathSuitInfo.append(oathSuit)
for skill in oathSkillEffect:
func = equs.funs.execture(f'oath_skill_{skill.id}')
if func is not None:
print(f'誓约技能 {skill.id} 生效')
func(self)
if self.max_point >= 2550:
skillAttack = (self.max_point - 2550) // 70 * 0.01
buffer = (self.max_point - 2550) // 70 * 100
self.SetStatus(SkillAttack=skillAttack, Buffer=buffer)
return res
def calc_equs(self):
"""计算装备基础效果、附魔"""
effects = get_equipment(self.equVersion).funs
# for detail in [(item.equInfo, item.fusionInfo, item.enchant, item.emblem_0, item.emblem_1) for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values())]:
for detail in [(item.equInfo, item.enchant) for item in filter(lambda x: x.equInfo is not None, self.charEquipInfo.values())]:
equ = detail[0]
if equ is None or equ.itemType == '副武器':
continue
enchat = detail[1]
fun = effects.execture(f'equ_{equ.id}')
if fun is None:
continue
filtered_dict = {k: v for k, v in equ.__dict__.items() if k[0].isupper()}
self.SetStatus(**filtered_dict)
fun(self)
fun = effects.execture(f'enchant_{enchat}')
if fun is not None:
fun(self)
# emblem_0 = detail[3]
# emblem_1 = detail[4]
# if str(emblem_0).isdigit():
# fun = effects.execture(f'emblem_{emblem_0}')
# if fun is not None:
# fun(self)
# else:
# skill = self.GetSkillByName(emblem_0)
# if skill is not None:
# skill.lv += 1
# self.SetStatus(四维=8)
# fun = effects.execture(f'emblem_{emblem_1}')
# if fun is not None:
# fun(self)
# if fusion is None:
# continue
# fun = effects.execture(f'stone_{fusion.id}')
# filtered_dict = {k: v for k, v in fusion.__dict__.items() if k[0].isupper()}
# self.SetStatus(**filtered_dict)
# if fun is not None:
# fun(self)
def calc_emblems(self):
"""计算徽章效果"""
effects = get_equipment(self.equVersion).funs
emblemCombine = effects.execture('emblem_combine')
for emblem in self.charEmblemInfo:
for item in emblem.items:
fun = effects.execture(f'emblem_{item}')
if fun is not None:
fun(self)
emblemCombine(self, emblem.part, emblem.lv, emblem.items)
pass
pass
def calc_oaths(self):
"""计算装备基础效果"""
effects = get_equipment(self.equVersion).funs
pass
def calc_avatar(self, avatar: dict):
"""计算时装效果"""
equ = get_equipment(self.equVersion)
dress = {key: value for key, value in avatar.items() if key in ['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋']}
equ.funs.calc_dress_effect(dress, self)
avatarElse = {key: value for key, value in avatar.items() if key not in ['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋']}
for key in avatarElse:
value = avatarElse[key].get('enchant', None)
emblem_0 = avatarElse[key].get('emblem_0', None)
emblem_1 = avatarElse[key].get('emblem_1', None)
if value is not None:
fun = equ.funs.execture(f'enchant_{value}')
if fun is not None:
fun(self)
for i in [emblem_0, emblem_1]:
if i is None:
continue
fun = equ.funs.execture(f'emblem_{i}')
if fun is not None:
fun(self)
def calc_weapon(self, cur: 'CharacterEquipInfo'):
"""武器强化、锻造计算"""
value_1 = 锻造计算(115, '史诗', cur.refine, cur.equInfo.itemDetailType, 1.12 if '传世武器' in cur.equInfo.categorize else 0)
value_2 = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '独立', 1.12 if '传世武器' in cur.equInfo.categorize else 0)
value = max(value_1, value_2)
self.SetStatus(AtkI=value)
if '传世武器' in cur.equInfo.categorize:
value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '物理', 1.12)
self.SetStatus(AtkP=value, AtkM=value)
else:
value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '物理')
self.SetStatus(AtkP=value)
value = 武器强化计算(115, '史诗', cur.reinforce, cur.equInfo.itemDetailType, '魔法')
self.SetStatus(AtkM=value)
def calc_basic(self):
"""计算基础属性:防具精通、增幅、强化等"""
for part in ['上衣', '头肩', '下装', '腰带', '鞋']:
cur = self.charEquipInfo[part]
if cur.equInfo is None:
continue
equ = cur.equInfo
reinforce = self.charEquipInfo[part].reinforce
if equ is None:
continue
for key in self.防具精通属性:
value = 精通计算(115, equ.rarity, reinforce, equ.itemDetailType, self.防具类型, self.buffer, key)
key = get_key_by_value(key)
setattr(self, key, getattr(self, key) + value)
for part in self.charEquipInfo.keys():
cur = self.charEquipInfo[part]
if cur.equInfo is None:
continue
if cur.reinforceType == 1:
value = 增幅计算(115, '史诗', cur.reinforce)
self.SetStatus(STR=value, INT=value, Spirit=value, Vitality=value)
value = 强化技攻(cur.reinforce, cur.reinforceType, part)
self.SetStatus(SkillAttack=value)
if '武器' in part:
self.calc_weapon(cur)
if part == '耳环':
value = 耳环计算(115, '史诗', cur.reinforce)
self.SetStatus(AtkM=value, AtkP=value, AtkI=value)
if part in ['辅助装备', '魔法石']:
value = 左右计算(115, '史诗', cur.reinforce)
self.SetStatus(STR=value, INT=value, Spirit=value, Vitality=value)
def calc_jade(self, jades):
"""计算辟邪玉效果"""
funs = get_equipment(self.equVersion).funs
for key in jades:
jade = jades[key]
fun = funs.execture(f'jade_{jade.get("id", 0)}')
if fun is not None:
fun(self, jade.get('value', 0))
def calc_sundry(self, sundry: dict):
"""计算杂项效果"""
funs = get_equipment(self.equVersion).funs
funs.execture('sundry_0')(
self,
sundry.get('medal_rarity', 0),
sundry.get('medal_reinforce', 0),
[sundry.get('medal_gem_0', 0), sundry.get('medal_gem_1', 0), sundry.get('medal_gem_2', 0), sundry.get('medal_gem_3', 0)],
)
funs.execture('sundry_6')(self, sundry.get('adventure', 1))
funs.execture('sundry_5')(self, sundry.get('fog', 1))
funs.execture('sundry_7')(self, sundry.get('marriage_house', 0), sundry.get('marriage_ring', 0))
funs.execture('sundry_9')(self, sundry.get('contract', 0))
funs.execture('sundry_10')(
self,
sundry.get('collection_type', 0),
sundry.get('collection_num_0', 0),
sundry.get('collection_num_1', 0),
)
funs.execture('sundry_13')(self, sundry.get('costume_card', 0))
# endregion
# region 主计算入口
def calc(self, setInfo: dict[str, dict]):
self.calc_init(setInfo)
self.SetBaseStatus()
self.calc_jade(setInfo.get('jades', []))
self.calc_avatar(setInfo.get('avatar', {}))
self.calc_basic()
self.calc_sundry(setInfo.get('sundry', {}))
self.calc_emblems()
suit = self.calc_suits()
self.calc_equs()
self.calc_oaths()
skillInfos = []
if self.buffer:
skillInfos = self.calc_buffer_skills()
info = self.get_buffer_info()
else:
if len(self.输出类型选项) > 1:
temp = self.输出类型选项[0]
if '固伤' in temp:
if self.INT > self.STR:
self.输出类型 = '魔法固伤'
else:
self.输出类型 = '物理固伤'
else:
if self.INT > self.STR and self.AtkM > self.AtkP:
self.输出类型 = '魔法百分比'
else:
self.输出类型 = '物理百分比'
skillInfos = self.calc_carry_skills(setInfo.get('DSB', False), setInfo.get('BUFF', False))
info = self.get_carry_info()
return {'uuid': uuid1().hex, 'skills': skillInfos, 'info': info, 'suits': suit, 'buffer': self.buffer}
# endregion
+147
View File
@@ -0,0 +1,147 @@
from core.basic.equipment import get_equipment
from core.basic.property import CharacterInfo
from .base import CharacterBase
class CharacterInfoMixin(CharacterBase):
"""角色信息查询相关方法(用于前端展示)"""
def getInfo(self):
"""返回到前端信息"""
info = {}
info['role'] = self.role
info['alter'] = self.name
info['equVersion'] = self.equVersion
info['name'] = self.nameCN
info['weapons'] = self.武器选项
info['subweapons'] = self.副武器选项
info['buffer'] = self.buffer
if self.buffer:
info['properties'] = self.防具精通属性
info['properties'] = self.输出类型选项
skillInfo = []
skill_clothes = []
platinum = []
for skill in self.skills:
if 15 <= skill.learnLv <= 70 and skill.learnLv not in [48, 50]:
platinum.append(skill.name)
if skill.learnLv <= 95:
skill_clothes.append(skill.name)
skillInfo.append(
{
'id': skill.id,
'name': skill.name,
'icon': skill.icon,
'type': skill.type,
'learnLv': skill.learnLv,
'masterLv': skill.masterLv,
'maxLearnLv': skill.calculate_lv(),
'maxLv': skill.maxLv,
'position': skill.position,
'line': skill.line,
'bind': skill.bind,
'hasVP': skill.hasVP,
'hasUP': skill.hasUP,
'upType': skill.upType,
'uuid': skill.uuid,
'vps': [{'name': i['name'], 'desc': i['desc']} for i in skill.vps],
}
)
info['skills'] = skillInfo
equInfos = get_equipment(self.equVersion)
equs = []
suits = []
oaths = []
oaths_skills = {}
# stones = []
for i in equInfos.equs:
if i.itemType == '武器' and i.itemDetailType not in self.武器选项:
continue
equs.append(i.__dict__)
for i in equInfos.suits:
suits.append(i.__dict__)
for i in equInfos.oaths:
oaths.append(i.__dict__)
for i in equInfos.oath_suits:
if i.suitId not in oaths_skills:
oaths_skills[i.suitId] = {'suitInfo': [], 'skills': []}
oaths_skills[i.suitId]['suitInfo'].append(
{
'name': i.name,
'rarity': i.rarity,
'SkillAttack': i.SkillAttack,
'Buffer': i.Buffer,
'Attack': i.Attack,
}
)
if len(oaths_skills[i.suitId]['skills']) == 0:
for skill in i.skills:
oaths_skills[i.suitId]['skills'].append(
{
'skillId': skill.skillId,
'name': skill.name,
'icon': skill.imageUrl,
}
)
# for i in equInfos.stones:
# stones.append(i.__dict__)
info['oaths'] = oaths
info['equips'] = equs
info['suits'] = suits
info['oaths_skills'] = oaths_skills
# info['stones'] = stones
key = '辅助' if self.buffer else '输出'
info['enchants'] = list(filter(lambda x: key in x['categorize'], equInfos.enchants))
info['emblems'] = [i for i in equInfos.emblems]
info['avatar'] = equInfos.funs.get_dress_list(skill_clothes)
info['jades'] = equInfos.jades
info['options'] = equInfos.funs.options
info['sundry'] = equInfos.funs.sundryList
for skill in platinum:
info['emblems'].append(
{
'id': skill,
'fame': 232,
'position': ['辅助装备', '魔法石'],
'detail': skill + ' Lv+1' + ' 四维 + 8',
'categorize': ['技能'],
'rarity': '白金',
}
)
return info
def getBasicInos(self):
res = []
attrs = []
if self.输出类型.includes('物理'):
attrs = ['STR', 'PSTR', 'AtkP', 'PAtkP', 'CriticalPP', 'CriticalP']
if self.输出类型.includes('魔法'):
attrs = ['INT', 'PINT', 'AtkM', 'PAtkM', 'CriticalMP', 'CriticalM']
for attr in attrs + ['PDamage', 'PDamageC', 'PDamageB', 'SpeedA', 'SpeedM', 'SpeedR']:
temp = getattr(CharacterInfo, attr)
res.append(
{
'name': temp.name,
'value': temp.value(getattr(self, attr)),
'icon': temp.icon,
}
)
keys = ['火', '冰', '光', '暗']
for index in range(0, 4):
temp = getattr(CharacterInfo, f'ElementDB{index}')
res.append(
{
'name': temp.name,
'value': temp.value(getattr(self, 'ElementDB')[keys[index]]),
'icon': temp.icon,
}
)
temp = getattr(CharacterInfo, f'ElementR{index}')
res.append(
{
'name': temp.name,
'value': temp.value(getattr(self, 'ElementR')[keys[index]]),
'icon': temp.icon,
}
)
return res
+12
View File
@@ -0,0 +1,12 @@
class Jade:
ElementIncrease: float
"""属性增幅"""
AttackP: float
"""攻击强化%"""
SkillAttack: float
"""技能攻击力%"""
def __init__(self) -> None:
self.ElementIncrease = 0.0
self.AttackP = 0.0
self.SkillAttack = 0.0
@@ -1,3 +1,15 @@
# ⚠️ 此文件已被 character/ 包目录取代。
# Python 会优先加载 character/__init__.py,此文件不会被 import 执行。
#
# 模块拆分结构(位于 core/basic/character/):
# jade.py — Jade 辟邪玉类
# base.py — CharacterBase(属性 / __init__ / 基础状态方法 / 技能操作)
# calc_equip.py — CalcEquipMixin(套装/装备/时装/强化/辟邪玉/杂项 + calc 主入口)
# calc_carry.py — CalcCarryMixin(输出伤害计算)
# calc_buffer.py — CalcBufferMixin(辅助 Buff 技能计算)
# info.py — CharacterInfoMixin(前端信息查询)
# __init__.py — 组装 Character 类 + createCharacter 工厂函数
from functools import cache
import importlib
import sys
@@ -8,7 +20,7 @@ from core.abstract.character import CharacterProperty
from core.basic.property import CharacterInfo
from core.basic.skill import Skill
from core.basic.equipment import get_equipment, EquEffect, Equ
from core.basic.equipment import Suit, get_equipment, EquEffect, Equ, OathSuit
# from core.basic.equipment import equipments
# from core.basic.skill import Skill, ActiveSkill, PassiveSkill
+63 -40
View File
@@ -80,24 +80,25 @@ class Equipments:
self.equs: list[Equ] = []
self.oaths: list[Oath] = []
self.oath_dict: dict[str, Oath] = {}
self.stones: list[Equ] = []
# self.stones: list[Equ] = []
self.funs = self.init_func()
self.equ_dict: dict[str, Equ] = {}
self.stone_dict: dict[str, Equ] = {}
# self.stone_dict: dict[str, Equ] = {}
self.jades = []
self.enchants = []
self.emblems = []
self.suits: list[Suit] = []
self.suit_dict: dict[str, Suit] = {}
self.oath_suits: list[Suit] = []
self.oath_suits: list[OathSuit] = []
self.oath_suit_dict: dict[str, OathSuit] = {}
self.init_equs()
self.init_suits()
self.init_enchants()
self.init_stones()
# self.init_stones()
self.init_emblem()
self.init_jades()
# self.init_oaths()
# self.init_oath_suits()
self.init_oaths()
self.init_oath_suits()
self.engine.dispose()
def init_equs(self):
@@ -123,36 +124,49 @@ class Equipments:
self.equs.append(equ)
self.equ_dict[equ.id] = equ
def init_stones(self):
"""从数据库中获取所有装备信息"""
with Session(self.engine) as session:
db_list = session.query(StoneData).all()
keys = [key for key in StoneData.__dict__.keys() if key[0].isupper()] + ['suit']
for item in db_list:
max_adaptation = 0
item.id = str(item.id)
for attr in keys:
if attr == 'suit':
value = parse_to_number_list(getattr(item, attr), [])
value = [str(int(i)) for i in value]
else:
value = parse_to_number_list(getattr(item, attr))
max_adaptation = max(max_adaptation, len(value) - 1)
setattr(item, attr, value)
stone_dict = {k: v for k, v in item.__dict__.items() if not k.startswith('_')}
stone_dict['max_adaptation'] = max_adaptation
stone = Equ(**stone_dict)
self.stones.append(stone)
self.stone_dict[stone.id] = stone
# def init_stones(self):
# """从数据库中获取所有装备信息"""
# with Session(self.engine) as session:
# db_list = session.query(StoneData).all()
# keys = [key for key in StoneData.__dict__.keys() if key[0].isupper()] + ['suit']
# for item in db_list:
# max_adaptation = 0
# item.id = str(item.id)
# for attr in keys:
# if attr == 'suit':
# value = parse_to_number_list(getattr(item, attr), [])
# value = [str(int(i)) for i in value]
# else:
# value = parse_to_number_list(getattr(item, attr))
# max_adaptation = max(max_adaptation, len(value) - 1)
# setattr(item, attr, value)
# stone_dict = {k: v for k, v in item.__dict__.items() if not k.startswith('_')}
# stone_dict['max_adaptation'] = max_adaptation
# stone = Equ(**stone_dict)
# self.stones.append(stone)
# self.stone_dict[stone.id] = stone
def init_suits(self):
"""从数据库中获取所有套装信息"""
with Session(self.engine) as session:
db_list = session.query(SuitData).all()
for item in db_list:
suit = Suit(**{k: v for k, v in item.__dict__.items() if not k.startswith('_')})
self.suits.append(suit)
self.suit_dict.setdefault(str(suit.suitId), []).append(suit)
lvs = len(item.point.split(','))
suits = {k: v for k, v in item.__dict__.items() if not k.startswith('_')}
for i in range(1, lvs + 1):
suit = {k: v for k, v in suits.items()}
suit['point'] = int(suit['point'].split(',')[i - 1])
suit['level'] = i
skillAttaks = parse_to_number_list(suit['SkillAttack'], [0])
suit['SkillAttack'] = 0 if i - 1 >= len(skillAttaks) else skillAttaks[i - 1]
attacks = parse_to_number_list(suit['Attack'], [0])
suit['Attack'] = 0 if i - 1 >= len(attacks) else attacks[i - 1]
buffers = parse_to_number_list(suit['Buffer'], [0])
suit['Buffer'] = 0 if i - 1 >= len(buffers) else buffers[i - 1]
suit_obj = Suit(**suit)
self.suits.append(suit_obj)
self.suit_dict.setdefault(str(suit['suitId']), []).append(suit_obj)
pass
pass
def init_enchants(self):
@@ -174,9 +188,9 @@ class Equipments:
db_list = session.query(EmblemData).all()
for item in db_list:
emblem = {k: v for k, v in item.__dict__.items() if not k.startswith('_')}
emblem['position'] = [] if emblem['itemType'] is None else emblem['itemType'].split(',')
emblem['categorize'] = [] if emblem['categorize'] is None else emblem['categorize'].split(',')
del emblem['itemType']
# emblem['position'] = [] if emblem['itemType'] is None else emblem['itemType'].split(',')
# emblem['categorize'] = [] if emblem['categorize'] is None else emblem['categorize'].split(',')
# del emblem['itemType']
self.emblems.append(emblem)
pass
pass
@@ -225,17 +239,20 @@ class Equipments:
suits = {k: v for k, v in item.__dict__.items() if not k.startswith('_')}
for i in range(1, lvs + 1):
suit = {k: v for k, v in suits.items()}
suit['points'] = suit['point']
suit['point'] = int(suit['point'].split(',')[i - 1])
skills = [x for x in db_skills_list if x.suitId == suit['suitId'] and x.key == suit['key']]
suit['level'] = i
skillAttaks = parse_to_number_list(suit['SkillAttack'], [0])
suit['SkillAttack'] = 0 if i - 1 >= len(skillAttaks) else skillAttaks[i - 1]
suit['SkillAttack'] = (0 if len(skillAttaks) == 0 else skillAttaks[0]) if i - 1 >= len(skillAttaks) else skillAttaks[i - 1]
attacks = parse_to_number_list(suit['Attack'], [0])
suit['Attack'] = 0 if i - 1 >= len(attacks) else attacks[i - 1]
suit['Attack'] = (0 if len(attacks) == 0 else attacks[0]) if i - 1 >= len(attacks) else attacks[i - 1]
buffers = parse_to_number_list(suit['Buffer'], [0])
suit['Buffer'] = 0 if i - 1 >= len(buffers) else buffers[i - 1]
suit['Buffer'] = (0 if len(buffers) == 0 else buffers[0]) if i - 1 >= len(buffers) else buffers[i - 1]
suit['skills'] = [OathSuitSkill(**{k: v for k, v in skill.__dict__.items() if not k.startswith('_')}) for skill in skills]
self.oath_suits.append(suit)
oath_suit = OathSuit(**suit)
self.oath_suits.append(oath_suit)
self.oath_suit_dict.setdefault(str(suit['suitId']), []).append(oath_suit)
pass
pass
@@ -263,7 +280,7 @@ class Equipments:
result[suit.count] = suit
return list(result.values())
def get_oath_suit_info(self, suitId: str | int, point: int = 0, count: int = 0) -> list[Suit]:
def get_oath_suit_info(self, suitId: str | int, point: int = 0, count: int = 0) -> list['OathSuit']:
"""根据套装点数返回对应适用的誓约套装属性\n
新套装只需要传入suitID和point即可,count默认为0\n
老套装需要传入suitID和count即可,point默认为0
@@ -271,8 +288,8 @@ class Equipments:
if suitId not in self.oath_suit_dict:
return []
# 先筛选出点数小于等于point和数量小于等于count的套装
suits: list[Suit] = [suit for suit in self.oath_suit_dict[str(suitId)] if suit.point <= point and suit.count <= count]
result: dict[int, Suit] = {}
suits: list[OathSuit] = [suit for suit in self.oath_suit_dict[str(suitId)] if suit.point <= point and suit.count <= count]
result: dict[int, OathSuit] = {}
# 取每种count套装中点数最高的
for suit in suits:
if suit.count not in result or suit.point > result[suit.count].point:
@@ -320,6 +337,8 @@ class Oath:
Buffer: list[float] | float
wearUrl: list[str]
max_adaptation: int
itemType: str
itemDetailType: str
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
@@ -354,6 +373,9 @@ class OathSuitSkill:
bufferValue: str
def __init__(self, **kwargs):
for key in ['SkillAttack', 'Attack', 'Buffer']:
if kwargs.get(key) is None:
kwargs[key] = 0
self.__dict__.update(kwargs)
@@ -365,6 +387,7 @@ class OathSuit:
rarity: str
name: str
point: int
points: str
level: int
count: int
imageUrl: str
+18 -10
View File
@@ -127,16 +127,17 @@ def 勋章计算(装备等级, 品质, 强化等级):
# region 武器
武器强化系数 = (0, 2, 2.6, 3.6, 4.7, 5.8, 6.9, 8.2, 11, 14.6, 18.7, 26.9, 36.7, 43, 49.2, 55.4, 61.7, 68, 74.3, 80.6, 86.9, 93.2, 99.5, 105.8, 112.1, 118.3, 124.6, 130.9, 137.1, 143.4, 149.7, 156.0)
武器锻造系数 = (0, 2, 3, 4, 6, 8, 13, 18, 25, 32, 41)
# 武器锻造系数 = (0, 2, 3, 4, 6, 8, 13, 18, 25, 32, 41)
武器锻造系数 = (0, 2.6, 3.6, 5.8, 8.2, 11, 18.7, 26.9, 42.1)
武器成长系数 = {'普通': 0.4, '高级': 0.7, '稀有': 1, '神器': 1.25, '勇者': 1.1, '传说': 1.35, '史诗': 1.45, '神话': 1.55}
武器品级加分 = {'普通': 8, '高级': 8, '稀有': 10, '神器': 12, '勇者': 11, '传说': 13, '史诗': 14, '神话': 15}
武器类型系数 = {
'短剑': (1.095, 1.115, 1.11),
'太刀': (1.095, 1.105, 1.1),
'钝器': (1.11, 1.095, 1.1),
'巨剑': (1.12, 1.09, 1.1),
'光剑': (1.093, 1.09, 1.1),
'手套': (1.095, 1.115, 1.1),
'太刀': (1.095, 1.105, 1.11),
'钝器': (1.11, 1.095, 1.11),
'巨剑': (1.12, 1.09, 1.11),
'光剑': (1.093, 1.09, 1.11),
'手套': (1.095, 1.115, 1.11),
'臂铠': (1.12, 1.09, 1.11),
'爪': (1.1, 1.1, 1.11),
'拳套': (1.105, 1.095, 1.11),
@@ -178,17 +179,24 @@ def 勋章计算(装备等级, 品质, 强化等级):
def 武器强化计算(装备等级, 品质, 强化等级, 武器类型, 类型, 默认系数=0):
index = 0 if 类型 == '物理' else (1 if 类型 == '魔法' else ())
index = 0
if 类型 == '物理':
index = 0
elif 类型 == '魔法':
index = 1
elif 类型 == '独立':
index = 2
武器系数 = max(武器类型系数[武器类型][index], 默认系数)
return int((装备等级 + 武器品级加分[品质]) / 8 * 武器成长系数[品质] * 武器强化系数[强化等级] * 武器系数)
def 锻造计算(装备等级, 品质, 锻造等级):
return round((装备等级 + 武器品级加分[品质]) / 8 * 武器成长系数[品质] * 武器锻造系数[锻造等级])
def 锻造计算(装备等级, 品质, 锻造等级, 武器类型, 默认系数=0):
武器系数 = max(武器类型系数[武器类型][2], 默认系数)
return int((装备等级 + 武器品级加分[品质]) / 8 * 武器成长系数[品质] * 武器锻造系数[锻造等级] * 武器系数)
def 锻造四维(装备等级, 品质, 锻造等级):
return round((装备等级 + 武器品级加分[品质]) / 80 * 武器成长系数[品质] * 武器锻造系数[锻造等级])
return round((装备等级 + 武器品级加分[品质]) / 130 * 武器成长系数[品质] * 武器锻造系数[锻造等级])
武器冷却惩罚系数 = {
+29 -17
View File
@@ -13,32 +13,32 @@ class CharacterEquipInfo:
"""强化类型"""
enchant: str
"""附魔"""
emblem_0: str
"""徽章1"""
emblem_1: str
"""徽章2"""
fusion: int | None
"""贴膜ID"""
# emblem_0: str
# """徽章1"""
# emblem_1: str
# """徽章2"""
# fusion: int | None
# """贴膜ID"""
refine: int | None
"""锻造等级"""
weaponFusion: int | None
"""武器融合详情"""
# weaponFusion: int | None
# """武器融合详情"""
adaptation: int
"""调适等级"""
equInfo: Union['Equ', None]
"""装备信息"""
fusionInfo: Union['Equ', None]
"""贴膜信息"""
# fusionInfo: Union['Equ', None]
# """贴膜信息"""
precision: float
"""秘宝精度"""
def __init__(self, info={}, equVerison=0, part='') -> None:
info['fusion'] = info.get('fusion', None)
# info['fusion'] = info.get('fusion', None)
info['precision'] = info.get('precision', 0)
info['weaponFusion'] = info.get('weaponFusion', None)
# info['weaponFusion'] = info.get('weaponFusion', None)
self.__dict__.update(info)
equ = get_equipment(equVerison).equ_dict.get(self.id, None)
fusion = get_equipment(equVerison).stone_dict.get(self.fusion, None)
# fusion = get_equipment(equVerison).stone_dict.get(self.fusion, None)
if equ is not None and (equ.itemType == part or equ.itemDetailType == part or (equ.itemType == '武器' and part == '副武器')):
self.equInfo = equ.adapt(self.adaptation)
self.adaptation = min(self.adaptation, self.equInfo.max_adaptation)
@@ -46,10 +46,10 @@ class CharacterEquipInfo:
self.equInfo.itemType = '副武器'
else:
self.equInfo = None
if fusion is not None and (fusion.itemType == part or fusion.itemDetailType == part):
self.fusionInfo = fusion.adapt(self.adaptation)
else:
self.fusionInfo = None
# if fusion is not None and (fusion.itemType == part or fusion.itemDetailType == part):
# self.fusionInfo = fusion.adapt(self.adaptation)
# else:
# self.fusionInfo = None
class CharacterOathInfo:
@@ -71,6 +71,18 @@ class CharacterOathInfo:
self.oathInfo = None
class CharacterEmblemInfo:
part: str
"""星徽部位"""
items: list[str]
"""徽章id列表"""
lv: int
"""星徽等级"""
def __init__(self, info={}) -> None:
self.__dict__.update(info)
class CharacterWeaponInfo(CharacterEquipInfo):
refine: int
"""锻造等级"""
+35 -2
View File
@@ -6,10 +6,18 @@ from . import emblem
from . import enchant
from . import sundry
from . import suit
from . import oathSuitSkill
from .register import execture
options = [{'id': 1, 'name': '龙战八荒', 'options': ['BUFF OFF', 'BUFF ON', '期望']}, {'id': 2, 'name': '混沌净化', 'options': ['净化', '堕落']}, {'id': 3, 'name': '群猎美学', 'options': ['单刷', '组队']}, {'id': 4, 'name': '魔攻核', 'options': ['普通', '追击']}, {'id': 5, 'name': '传世手杖', 'options': ['BUFF ON', 'BUFF OFF', '期望']}, {'id': 6, 'name': '传世嵌合弓', 'options': ['本能飞跃', '非理性着陆']}]
options = [
{'id': 1, 'name': '龙战八荒', 'options': ['BUFF OFF', 'BUFF ON', '期望']},
{'id': 2, 'name': '混沌净化', 'options': ['净化', '堕落']},
{'id': 3, 'name': '群猎美学', 'options': ['单刷', '组队']},
{'id': 4, 'name': '魔攻核', 'options': ['普通', '追击']},
{'id': 5, 'name': '传世手杖', 'options': ['BUFF ON', 'BUFF OFF', '期望']},
{'id': 6, 'name': '传世嵌合弓', 'options': ['本能飞跃', '非理性着陆']},
]
sundryList = {
'medal_rarity': {'id': 0, 'options': [{'name': f'品质:{i}', 'value': idx} for idx, i in enumerate(['普通', '高级', '稀有', '神器', '传说', '史诗'])]},
@@ -28,4 +36,29 @@ sundryList = {
'costume_card': {'id': 13, 'options': [{'name': f'{i}', 'value': idx} for idx, i in enumerate(['无', '四维(3)|三速(1%)'])]},
}
__all__ = [get_dress_list, calc_dress_effect, options, execture, sundryList, equ, stone, jade, emblem, enchant, sundry, suit]
__all__ = [get_dress_list, calc_dress_effect, options, execture, sundryList, equ, stone, jade, emblem, enchant, sundry, suit, oathSuitSkill]
def get_reinforce_sum(char):
reinforce_0 = 0
"""防具"""
reinforce_1 = 0
"""首饰"""
reinforce_2 = 0
"""特殊装备"""
reinforce_3 = 0
"""武器"""
for part in char.charEquipInfo.keys():
if char.charEquipInfo[part] is None:
continue
num = char.charEquipInfo[part].reinforce
# if part == '武器':
# reinforce_3 += num
if part in ['上衣', '头肩', '下装', '腰带', '鞋']:
reinforce_0 += num
if part in ['手镯', '项链', '戒指']:
reinforce_1 += num
if part in ['耳环', '辅助装备', '魔法石']:
reinforce_2 += num
reinforce = reinforce_0 + reinforce_1 + reinforce_2 + reinforce_3
return reinforce
+486 -156
View File
@@ -1,235 +1,565 @@
from core.abstract.character import CharacterProperty
from .register import register
emblem_func_list = {}
@register
def emblem_1(char: CharacterProperty):
def emblem_2101(char: CharacterProperty):
# DCALC_REMOVE: emblem_1
# 力智+20 暴击+1.9%
# rarity: 玲珑
char.SetStatus(力智=20)
pass
@register
def emblem_2(char: CharacterProperty):
# DCALC_REMOVE: emblem_2
# 力智+15 暴击+1.5%
# rarity: 玲珑
char.SetStatus(力智=15)
pass
@register
def emblem_3(char: CharacterProperty):
# DCALC_REMOVE: emblem_3
# 力智+10 暴击+1.1%
# rarity: 玲珑
char.SetStatus(力智=10)
pass
@register
def emblem_4(char: CharacterProperty):
# DCALC_REMOVE: emblem_4
# 力智+35
# rarity: 玲珑
# 力智 +35
# rarity: 灿烂 (红色)
char.SetStatus(力智=35)
pass
@register
def emblem_5(char: CharacterProperty):
# DCALC_REMOVE: emblem_5
# 体精+35
# rarity: 玲珑
def emblem_2102(char: CharacterProperty):
# DCALC_REMOVE: emblem_2
# 体精 +35
# rarity: 灿烂 (红色)
char.SetStatus(体精=35)
pass
@register
def emblem_6(char: CharacterProperty):
# DCALC_REMOVE: emblem_6
# 体精+25
# rarity: 玲珑
char.SetStatus(体精=25)
def emblem_2103(char: CharacterProperty):
# DCALC_REMOVE: emblem_3
# 四维 +15
# rarity: 灿烂 (红色)
char.SetStatus(四维=15)
pass
@register
def emblem_7(char: CharacterProperty):
# DCALC_REMOVE: emblem_7
# 体精+17
# rarity: 玲珑
char.SetStatus(体精=17)
pass
@register
def emblem_8(char: CharacterProperty):
# DCALC_REMOVE: emblem_8
# 力智+30
# rarity: 玲珑
char.SetStatus(力智=30)
pass
@register
def emblem_9(char: CharacterProperty):
# DCALC_REMOVE: emblem_9
# 攻速+2%
# rarity: 玲珑
char.SetStatus(攻速=0.02)
pass
@register
def emblem_10(char: CharacterProperty):
# DCALC_REMOVE: emblem_10
# 体精+30
# rarity: 玲珑
char.SetStatus(体精=30)
pass
@register
def emblem_11(char: CharacterProperty):
# DCALC_REMOVE: emblem_11
# 三攻+20
# rarity: 玲珑
char.SetStatus(三攻=20)
pass
@register
def emblem_12(char: CharacterProperty):
# DCALC_REMOVE: emblem_12
# 力智+30
# rarity: 玲珑
char.SetStatus(力智=30)
pass
@register
def emblem_13(char: CharacterProperty):
# DCALC_REMOVE: emblem_13
# 体精+30
# rarity: 玲珑
char.SetStatus(体精=30)
pass
@register
def emblem_14(char: CharacterProperty):
# DCALC_REMOVE: emblem_14
# 力智+25
# rarity: 灿烂
def emblem_2201(char: CharacterProperty):
# DCALC_REMOVE: emblem_4
# 力智 +25
# rarity: 灿烂 (黄色)
char.SetStatus(力智=25)
pass
@register
def emblem_15(char: CharacterProperty):
def emblem_2202(char: CharacterProperty):
# DCALC_REMOVE: emblem_5
# 体精 +25
# rarity: 灿烂 (黄色)
char.SetStatus(体精=25)
pass
@register
def emblem_2203(char: CharacterProperty):
# DCALC_REMOVE: emblem_6
# 攻速 +1.5% 四维 +10
# rarity: 灿烂 (黄色)
char.SetStatus(攻速=0.015, 四维=10)
pass
@register
def emblem_2204(char: CharacterProperty):
# DCALC_REMOVE: emblem_7
# 施放 +2.2% 四维 +10
# rarity: 灿烂 (黄色)
char.SetStatus(施放=0.022, 四维=10)
pass
@register
def emblem_2205(char: CharacterProperty):
# DCALC_REMOVE: emblem_8
# 四维 +10
# rarity: 灿烂 (黄色)
char.SetStatus(四维=10)
pass
@register
def emblem_2301(char: CharacterProperty):
# DCALC_REMOVE: emblem_9
# 力智 +25
# rarity: 灿烂 (绿色)
char.SetStatus(力智=25)
pass
@register
def emblem_2302(char: CharacterProperty):
# DCALC_REMOVE: emblem_10
# 体精 +25
# rarity: 灿烂 (绿色)
char.SetStatus(体精=25)
pass
@register
def emblem_2303(char: CharacterProperty):
# DCALC_REMOVE: emblem_11
# 暴击 +3% 四维 +10
# rarity: 灿烂 (绿色)
char.SetStatus(暴击=0.030, 四维=10)
pass
@register
def emblem_2304(char: CharacterProperty):
# DCALC_REMOVE: emblem_12
# 四维 +10
# rarity: 灿烂 (绿色)
char.SetStatus(四维=10)
pass
@register
def emblem_2401(char: CharacterProperty):
# DCALC_REMOVE: emblem_13
# 三攻 +30
# rarity: 灿烂 (蓝色)
char.SetStatus(三攻=30)
pass
@register
def emblem_2402(char: CharacterProperty):
# DCALC_REMOVE: emblem_14
# 移动 +1.5% 四维 +10
# rarity: 灿烂 (蓝色)
char.SetStatus(移动=0.015, 四维=10)
pass
@register
def emblem_2403(char: CharacterProperty):
# DCALC_REMOVE: emblem_15
# 力智+15
# rarity: 灿烂
char.SetStatus(力智=15)
# 四维 +10
# rarity: 灿烂 (蓝色)
char.SetStatus(四维=10)
pass
@register
def emblem_16(char: CharacterProperty):
def emblem_2501(char: CharacterProperty):
# DCALC_REMOVE: emblem_16
# 攻速+1.5%
# rarity: 灿烂
char.SetStatus(攻速=0.015)
# 四维 +10
# rarity: 灿烂 (彩色)
char.SetStatus(四维=10)
pass
@register
def emblem_17(char: CharacterProperty):
def emblem_2601(char: CharacterProperty):
# DCALC_REMOVE: emblem_17
# 体精+15
# rarity: 灿烂
char.SetStatus(体精=15)
# 力智 +20 暴击 +1.5%
# rarity: 灿烂 (红绿)
char.SetStatus(力智=20, 暴击=0.015)
pass
@register
def emblem_18(char: CharacterProperty):
def emblem_2602(char: CharacterProperty):
# DCALC_REMOVE: emblem_18
# 三攻+15
# rarity: 灿烂
char.SetStatus(三攻=15)
# 力智 +20
# rarity: 灿烂 (红绿)
char.SetStatus(力智=20)
pass
@register
def emblem_19(char: CharacterProperty):
def emblem_2701(char: CharacterProperty):
# DCALC_REMOVE: emblem_19
# 体精+15
# rarity: 灿烂
char.SetStatus(体精=15)
# 四维 +10
# rarity: 灿烂 (黄蓝)
char.SetStatus(四维=10)
pass
@register
def emblem_20(char: CharacterProperty):
def emblem_3101(char: CharacterProperty):
# DCALC_REMOVE: emblem_20
# 力智+17
# rarity: 华丽
char.SetStatus(力智=17)
# 力智 +50
# rarity: 玲珑 (红色)
char.SetStatus(力智=50)
pass
@register
def emblem_21(char: CharacterProperty):
def emblem_3102(char: CharacterProperty):
# DCALC_REMOVE: emblem_21
# 力智+10
# rarity: 华丽
char.SetStatus(力智=10)
# 体精 +50
# rarity: 玲珑 (红色)
char.SetStatus(体精=50)
pass
@register
def emblem_22(char: CharacterProperty):
def emblem_3103(char: CharacterProperty):
# DCALC_REMOVE: emblem_22
# 攻速+1.1%
# rarity: 华丽
char.SetStatus(攻速=0.011)
# 四维 +20
# rarity: 玲珑 (红色)
char.SetStatus(四维=20)
pass
@register
def emblem_23(char: CharacterProperty):
def emblem_3201(char: CharacterProperty):
# DCALC_REMOVE: emblem_23
# 体精+10
# rarity: 华丽
char.SetStatus(体精=10)
# 力智 +40
# rarity: 玲珑 (黄色)
char.SetStatus(力智=40)
pass
@register
def emblem_24(char: CharacterProperty):
def emblem_3202(char: CharacterProperty):
# DCALC_REMOVE: emblem_24
# 三攻+10
# rarity: 华丽
char.SetStatus(三攻=10)
# 体精 +40
# rarity: 玲珑 (黄色)
char.SetStatus(体精=40)
pass
@register
def emblem_25(char: CharacterProperty):
def emblem_3203(char: CharacterProperty):
# DCALC_REMOVE: emblem_25
# 体精+10
# rarity: 华丽
char.SetStatus(体精=10)
# 攻速 +2% 四维 +15
# rarity: 玲珑 (黄色)
char.SetStatus(攻速=0.020, 四维=15)
pass
for i in range(0, 3000):
try:
if str(i) not in emblem_func_list.keys():
emblem_func_list[str(i)] = eval(f'emblem_{i}')
except Exception:
@register
def emblem_3204(char: CharacterProperty):
# DCALC_REMOVE: emblem_26
# 施放 +3% 四维 +15
# rarity: 玲珑 (黄色)
char.SetStatus(施放=0.030, 四维=15)
pass
@register
def emblem_3205(char: CharacterProperty):
# DCALC_REMOVE: emblem_27
# 四维 +15
# rarity: 玲珑 (黄色)
char.SetStatus(四维=15)
pass
@register
def emblem_3301(char: CharacterProperty):
# DCALC_REMOVE: emblem_28
# 力智 +40
# rarity: 玲珑 (绿色)
char.SetStatus(力智=40)
pass
@register
def emblem_3302(char: CharacterProperty):
# DCALC_REMOVE: emblem_29
# 体精 +40
# rarity: 玲珑 (绿色)
char.SetStatus(体精=40)
pass
@register
def emblem_3303(char: CharacterProperty):
# DCALC_REMOVE: emblem_30
# 暴击 +4% 四维 +15
# rarity: 玲珑 (绿色)
char.SetStatus(暴击=0.040, 四维=15)
pass
@register
def emblem_3304(char: CharacterProperty):
# DCALC_REMOVE: emblem_31
# 四维 +15
# rarity: 玲珑 (绿色)
char.SetStatus(四维=15)
pass
@register
def emblem_3401(char: CharacterProperty):
# DCALC_REMOVE: emblem_32
# 三攻 +40
# rarity: 玲珑 (蓝色)
char.SetStatus(三攻=40)
pass
@register
def emblem_3402(char: CharacterProperty):
# DCALC_REMOVE: emblem_33
# 移动 +2% 四维 +15
# rarity: 玲珑 (蓝色)
char.SetStatus(移动=0.020, 四维=15)
pass
@register
def emblem_3403(char: CharacterProperty):
# DCALC_REMOVE: emblem_34
# 四维 +15
# rarity: 玲珑 (蓝色)
char.SetStatus(四维=15)
pass
@register
def emblem_3501(char: CharacterProperty):
# DCALC_REMOVE: emblem_35
# 四维 +15
# rarity: 玲珑 (彩色)
char.SetStatus(四维=15)
pass
@register
def emblem_3601(char: CharacterProperty):
# DCALC_REMOVE: emblem_36
# 力智 +25 暴击 +1.9%
# rarity: 玲珑 (红绿)
char.SetStatus(力智=25, 暴击=0.019)
pass
@register
def emblem_3602(char: CharacterProperty):
# DCALC_REMOVE: emblem_37
# 力智 +25
# rarity: 玲珑 (红绿)
char.SetStatus(力智=25)
pass
@register
def emblem_3701(char: CharacterProperty):
# DCALC_REMOVE: emblem_38
# 四维 +15
# rarity: 玲珑 (黄蓝)
char.SetStatus(四维=15)
pass
@register
def emblem_4101(char: CharacterProperty):
# DCALC_REMOVE: emblem_39
# 力智 +70
# rarity: 神圣 (红色)
char.SetStatus(力智=70)
pass
@register
def emblem_4102(char: CharacterProperty):
# DCALC_REMOVE: emblem_40
# 体精 +70
# rarity: 神圣 (红色)
char.SetStatus(体精=70)
pass
@register
def emblem_4103(char: CharacterProperty):
# DCALC_REMOVE: emblem_41
# 四维 +25
# rarity: 神圣 (红色)
char.SetStatus(四维=25)
pass
@register
def emblem_4201(char: CharacterProperty):
# DCALC_REMOVE: emblem_42
# 力智 +55
# rarity: 神圣 (黄色)
char.SetStatus(力智=55)
pass
@register
def emblem_4202(char: CharacterProperty):
# DCALC_REMOVE: emblem_43
# 体精 +55
# rarity: 神圣 (黄色)
char.SetStatus(体精=55)
pass
@register
def emblem_4203(char: CharacterProperty):
# DCALC_REMOVE: emblem_44
# 攻速 +2.5% 四维 +20
# rarity: 神圣 (黄色)
char.SetStatus(攻速=0.025, 四维=20)
pass
@register
def emblem_4204(char: CharacterProperty):
# DCALC_REMOVE: emblem_45
# 施放 +4% 四维 +20
# rarity: 神圣 (黄色)
char.SetStatus(施放=0.040, 四维=20)
pass
@register
def emblem_4205(char: CharacterProperty):
# DCALC_REMOVE: emblem_46
# 四维 +20
# rarity: 神圣 (黄色)
char.SetStatus(四维=20)
pass
@register
def emblem_4301(char: CharacterProperty):
# DCALC_REMOVE: emblem_47
# 力智 +55
# rarity: 神圣 (绿色)
char.SetStatus(力智=55)
pass
@register
def emblem_4302(char: CharacterProperty):
# DCALC_REMOVE: emblem_48
# 体精 +55
# rarity: 神圣 (绿色)
char.SetStatus(体精=55)
pass
@register
def emblem_4303(char: CharacterProperty):
# DCALC_REMOVE: emblem_49
# 暴击 +3% 四维 +20
# rarity: 神圣 (绿色)
char.SetStatus(暴击=0.030, 四维=20)
pass
@register
def emblem_4304(char: CharacterProperty):
# DCALC_REMOVE: emblem_50
# 四维 +20
# rarity: 神圣 (绿色)
char.SetStatus(四维=20)
pass
@register
def emblem_4401(char: CharacterProperty):
# DCALC_REMOVE: emblem_51
# 三攻 +60
# rarity: 神圣 (蓝色)
char.SetStatus(三攻=60)
pass
@register
def emblem_4402(char: CharacterProperty):
# DCALC_REMOVE: emblem_52
# 移动 +2.5% 四维 +20
# rarity: 神圣 (蓝色)
char.SetStatus(移动=0.025, 四维=20)
pass
@register
def emblem_4403(char: CharacterProperty):
# DCALC_REMOVE: emblem_53
# 四维 +20
# rarity: 神圣 (蓝色)
char.SetStatus(四维=20)
pass
@register
def emblem_4501(char: CharacterProperty):
# DCALC_REMOVE: emblem_54
# 四维 +20
# rarity: 神圣 (彩色)
char.SetStatus(四维=20)
pass
@register
def emblem_4601(char: CharacterProperty):
# DCALC_REMOVE: emblem_55
# 力智 +30 暴击 +2.5%
# rarity: 神圣 (红绿)
char.SetStatus(力智=30, 暴击=0.025)
pass
@register
def emblem_4602(char: CharacterProperty):
# DCALC_REMOVE: emblem_56
# 力智 +30
# rarity: 神圣 (红绿)
char.SetStatus(力智=30)
pass
@register
def emblem_4701(char: CharacterProperty):
# DCALC_REMOVE: emblem_57
# 四维 +20
# rarity: 神圣 (黄蓝)
char.SetStatus(四维=20)
pass
@register
def emblem_combine(char: CharacterProperty, position: str, lv: int = 1, emblem_list: list[int | str] = []):
bufferP = [0.001, 0.002, 0.004, 0.006, 0.008, 0.01, 0.013, 0.016, 0.019, 0.022, 0.025, 0.026, 0.027, 0.028, 0.029, 0.03]
buffer = [10, 30, 60, 90, 120, 150, 180, 210, 240270, 300, 320, 340, 360, 380, 400]
skillAttack = [0.002, 0.004, 0.008, 0.012, 0.016, 0.02, 0.023, 0.026, 0.029, 0.032, 0.035, 0.036, 0.037, 0.038, 0.039, 0.04]
attackP = [0.005, 0.009, 0.013, 0.017, 0.021, 0.025, 0.028, 0.031, 0.034, 0.037, 0.04, 0.042, 0.044, 0.046, 0.048, 0.05]
if position == '白金':
return
# 固有属性
if position == '彩色':
char.SetStatus(BufferP=bufferP[lv - 1], SkillAttack=skillAttack[lv - 1])
else:
char.SetStatus(Buffer=buffer[lv - 1], AttackP=attackP[lv - 1])
rarity = (lv - 1) // 5
emblems = list(filter(lambda x: safeParseInt(x) > 0, emblem_list)) or []
emblem_raritys = [safeParseInt(i) // 1000 for i in emblems]
# 稀有
if rarity == 0:
skillAttack = [0.001, 0.001, 0.002, 0.002]
buffer = [10, 10, 20, 20]
# 神器
elif rarity == 1:
skillAttack = [0.001, 0.002, 0.003, 0.004]
buffer = [10, 20, 30, 40]
pass
# 传说
elif rarity == 2:
skillAttack = [0.002, 0.003, 0.004, 0.005]
buffer = [20, 30, 40, 50]
pass
# 史诗
elif rarity == 3:
skillAttack = [0.002, 0.003, 0.005, 0.006]
buffer = [20, 30, 50, 60]
pass
SkillAttack = 0
Buffer = 0
for i in emblem_raritys:
SkillAttack += skillAttack[i - 1]
Buffer += buffer[i - 1]
char.SetStatus(SkillAttack=SkillAttack, Buffer=Buffer)
def safeParseInt(x: int | str | None) -> int:
try:
return int(x)
except Exception:
return 0
+543
View File
@@ -0,0 +1,543 @@
from core.basic.equipment import EquEffect
from core.abstract.character import CharacterProperty
from .register import register
@register
def oath_2010100(char: CharacterProperty):
pass
@register
def oath_2010200(char: CharacterProperty):
pass
@register
def oath_2010201(char: CharacterProperty):
pass
@register
def oath_2010300(char: CharacterProperty):
pass
@register
def oath_2010301(char: CharacterProperty):
pass
@register
def oath_2010400(char: CharacterProperty):
pass
@register
def oath_2010401(char: CharacterProperty):
pass
@register
def oath_2010500(char: CharacterProperty):
pass
@register
def oath_2010501(char: CharacterProperty):
pass
@register
def oath_2020100(char: CharacterProperty):
pass
@register
def oath_2020200(char: CharacterProperty):
pass
@register
def oath_2020201(char: CharacterProperty):
pass
@register
def oath_2020300(char: CharacterProperty):
pass
@register
def oath_2020301(char: CharacterProperty):
pass
@register
def oath_2020400(char: CharacterProperty):
pass
@register
def oath_2020401(char: CharacterProperty):
pass
@register
def oath_2020500(char: CharacterProperty):
pass
@register
def oath_2020501(char: CharacterProperty):
pass
@register
def oath_2030100(char: CharacterProperty):
pass
@register
def oath_2030200(char: CharacterProperty):
pass
@register
def oath_2030201(char: CharacterProperty):
pass
@register
def oath_2030300(char: CharacterProperty):
pass
@register
def oath_2030301(char: CharacterProperty):
pass
@register
def oath_2030400(char: CharacterProperty):
pass
@register
def oath_2030401(char: CharacterProperty):
pass
@register
def oath_2030500(char: CharacterProperty):
pass
@register
def oath_2030501(char: CharacterProperty):
pass
@register
def oath_2040100(char: CharacterProperty):
pass
@register
def oath_2040200(char: CharacterProperty):
pass
@register
def oath_2040201(char: CharacterProperty):
pass
@register
def oath_2040300(char: CharacterProperty):
pass
@register
def oath_2040301(char: CharacterProperty):
pass
@register
def oath_2040400(char: CharacterProperty):
pass
@register
def oath_2040401(char: CharacterProperty):
pass
@register
def oath_2040500(char: CharacterProperty):
pass
@register
def oath_2040501(char: CharacterProperty):
pass
@register
def oath_2050100(char: CharacterProperty):
pass
@register
def oath_2050200(char: CharacterProperty):
pass
@register
def oath_2050201(char: CharacterProperty):
pass
@register
def oath_2050300(char: CharacterProperty):
pass
@register
def oath_2050301(char: CharacterProperty):
pass
@register
def oath_2050400(char: CharacterProperty):
pass
@register
def oath_2050401(char: CharacterProperty):
pass
@register
def oath_2050500(char: CharacterProperty):
pass
@register
def oath_2050501(char: CharacterProperty):
pass
@register
def oath_2060100(char: CharacterProperty):
pass
@register
def oath_2060200(char: CharacterProperty):
pass
@register
def oath_2060201(char: CharacterProperty):
pass
@register
def oath_2060300(char: CharacterProperty):
pass
@register
def oath_2060301(char: CharacterProperty):
pass
@register
def oath_2060400(char: CharacterProperty):
pass
@register
def oath_2060401(char: CharacterProperty):
pass
@register
def oath_2060500(char: CharacterProperty):
pass
@register
def oath_2060501(char: CharacterProperty):
pass
@register
def oath_2070100(char: CharacterProperty):
pass
@register
def oath_2070200(char: CharacterProperty):
pass
@register
def oath_2070201(char: CharacterProperty):
pass
@register
def oath_2070300(char: CharacterProperty):
pass
@register
def oath_2070301(char: CharacterProperty):
pass
@register
def oath_2070400(char: CharacterProperty):
pass
@register
def oath_2070401(char: CharacterProperty):
pass
@register
def oath_2070500(char: CharacterProperty):
pass
@register
def oath_2070501(char: CharacterProperty):
pass
@register
def oath_2080100(char: CharacterProperty):
pass
@register
def oath_2080200(char: CharacterProperty):
pass
@register
def oath_2080201(char: CharacterProperty):
pass
@register
def oath_2080300(char: CharacterProperty):
pass
@register
def oath_2080301(char: CharacterProperty):
pass
@register
def oath_2080400(char: CharacterProperty):
pass
@register
def oath_2080401(char: CharacterProperty):
pass
@register
def oath_2080500(char: CharacterProperty):
pass
@register
def oath_2080501(char: CharacterProperty):
pass
@register
def oath_2090100(char: CharacterProperty):
pass
@register
def oath_2090200(char: CharacterProperty):
pass
@register
def oath_2090201(char: CharacterProperty):
pass
@register
def oath_2090300(char: CharacterProperty):
pass
@register
def oath_2090301(char: CharacterProperty):
pass
@register
def oath_2090400(char: CharacterProperty):
pass
@register
def oath_2090401(char: CharacterProperty):
pass
@register
def oath_2090500(char: CharacterProperty):
pass
@register
def oath_2090501(char: CharacterProperty):
pass
@register
def oath_2100100(char: CharacterProperty):
pass
@register
def oath_2100200(char: CharacterProperty):
pass
@register
def oath_2100201(char: CharacterProperty):
pass
@register
def oath_2100300(char: CharacterProperty):
pass
@register
def oath_2100301(char: CharacterProperty):
pass
@register
def oath_2100400(char: CharacterProperty):
pass
@register
def oath_2100401(char: CharacterProperty):
pass
@register
def oath_2100500(char: CharacterProperty):
pass
@register
def oath_2100501(char: CharacterProperty):
pass
@register
def oath_2110100(char: CharacterProperty):
pass
@register
def oath_2110200(char: CharacterProperty):
pass
@register
def oath_2110201(char: CharacterProperty):
pass
@register
def oath_2110300(char: CharacterProperty):
pass
@register
def oath_2110301(char: CharacterProperty):
pass
@register
def oath_2110400(char: CharacterProperty):
pass
@register
def oath_2110401(char: CharacterProperty):
pass
@register
def oath_2110500(char: CharacterProperty):
pass
@register
def oath_2110501(char: CharacterProperty):
pass
@register
def oath_2120100(char: CharacterProperty):
pass
@register
def oath_2120200(char: CharacterProperty):
pass
@register
def oath_2120201(char: CharacterProperty):
pass
@register
def oath_2120300(char: CharacterProperty):
pass
@register
def oath_2120301(char: CharacterProperty):
pass
@register
def oath_2120400(char: CharacterProperty):
pass
@register
def oath_2120401(char: CharacterProperty):
pass
@register
def oath_2120500(char: CharacterProperty):
pass
@register
def oath_2120501(char: CharacterProperty):
pass
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -79,12 +79,12 @@ job_mapping = {
async def get_user_info_by_id(user_id: str, alter: AltersDep):
if alter is None:
raise ResponseException("登录过期或无效Token,请刷新后重试")
raise ResponseException('登录过期或无效Token,请刷新后重试')
name = next((k for k, v in job_mapping.items() if v == alter.origin), '')
# 通过OCR助手获取用户信息
helper_info = await get_main_user_info_by_id(user_id, name)
if helper_info is None:
raise ResponseException("当前助手ID不存在或已隐藏,请确认后重试")
raise ResponseException('当前助手ID不存在或已隐藏,请确认后重试')
else:
return trans_to_dcalc_set(helper_info, alter)
pass
@@ -93,7 +93,7 @@ async def get_user_info_by_id(user_id: str, alter: AltersDep):
def trans_to_dcalc_set(detail, alter: AltersDep):
job = job_mapping.get(detail.get('job', ''), '')
if job != alter.origin:
raise ResponseException("未能匹配到当前职业对应的角色信息,请确认后重试")
raise ResponseException('未能匹配到当前职业对应的角色信息,请确认后重试')
basic = get_equipment('0')
basicAvatars = basic.funs.get_dress_list([])
equs = detail.get('equs', [])
@@ -126,9 +126,9 @@ def trans_to_dcalc_set(detail, alter: AltersDep):
# 贴膜
if dcalcEqu:
info['id'] = dcalcEqu.id
dcalcEquFusion = next((item for item in basic.stones if item.name == equ.get('upgrade', '')), None)
if dcalcEquFusion:
info['fusion'] = dcalcEquFusion.id
# dcalcEquFusion = next((item for item in basic.stones if item.name == equ.get('upgrade', '')), None)
# if dcalcEquFusion:
# info['fusion'] = dcalcEquFusion.id
info['enchant'] = getEnchatIdByName(equ.get('enchant', ''), equ.get('posName', ''), basic.enchants)
emblems = [getEmblemIdByName(i.get('detail', ''), i.get('categorize', ''), i.get('rarity', ''), basic.emblems) for i in equ.get('emblems', [])]
info['emblem_0'] = emblems[0] if len(emblems) > 0 else 0
@@ -167,7 +167,7 @@ def trans_to_dcalc_set(detail, alter: AltersDep):
if item.get('posName', '') == '皮肤':
match = re.search(r'\[(\d+)级\]', item.get('name', ''))
lv = int(match.group(1)) if match else None
if lv :
if lv:
item['enchant'] = f'Lv{lv}主动+1|全属强(12)|四维(55)'
if item.get('posName', '') == '光环':
# 先默认这个了 不管了
@@ -182,11 +182,14 @@ def trans_to_dcalc_set(detail, alter: AltersDep):
'equips': equips,
'avatars': avatar,
'avatarLink': detail.get('avatarLink', ''),
'dressInfo': [{
'dress': i.get('name', ''),
'clone': i.get('clone', ''),
'part': i.get('posName', ''),
} for i in detail.get('avatars', [])]
'dressInfo': [
{
'dress': i.get('name', ''),
'clone': i.get('clone', ''),
'part': i.get('posName', ''),
}
for i in detail.get('avatars', [])
],
}
# 处理装备
# 处理时装
Binary file not shown.
+26 -11
View File
@@ -2,7 +2,7 @@
# import sys
from sqlalchemy import Column, Integer, Text, Float
from sqlalchemy import create_engine
from sqlalchemy.orm import Session,declarative_base
from sqlalchemy.orm import Session, declarative_base
Base = declarative_base()
metadata = Base.metadata
@@ -16,17 +16,17 @@ class SuitData(Base):
suitName = Column(Text)
rarity = Column(Text)
name = Column(Text)
point = Column(Integer)
level = Column(Integer)
point = Column(Text)
count = Column(Integer)
SkillAttack = Column(Float)
Attack = Column(Float)
Buffer = Column(Float)
SkillAttack = Column(Text)
Attack = Column(Text)
Buffer = Column(Text)
imageUrl = Column(Text)
value = Column(Text)
bufferValue = Column(Text)
fame = Column(Integer)
class EquData(Base):
"""
装备属性映射
@@ -57,6 +57,7 @@ class EquData(Base):
Buffer = Column(Text)
suit = Column(Text)
class StoneData(Base):
"""
装备属性映射
@@ -87,6 +88,7 @@ class StoneData(Base):
Buffer = Column(Text)
suit = Column(Text)
class EnchantData(Base):
__tablename__ = 'enchant'
@@ -97,15 +99,18 @@ class EnchantData(Base):
fame = Column(Integer)
rarity = Column(Text)
class EmblemData(Base):
__tablename__ = 'emblem'
id = Column(Integer, primary_key=True)
detail = Column(Text)
categorize = Column(Integer)
itemType = Column(Text)
fame = Column(Integer)
rarityId = Column(Integer)
rarity = Column(Text)
typeId = Column(Integer)
type = Column(Text)
value = Column(Text)
fame = Column(Integer)
class JadeData(Base):
__tablename__ = 'jade'
@@ -117,11 +122,13 @@ class JadeData(Base):
unit = Column(Text)
pre = Column(Float)
class OathData(Base):
"""
誓约属性映射
首字母大写的属性为调适可能会影响到的属性
"""
__tablename__ = 'oath'
id = Column(Integer, primary_key=True)
@@ -129,6 +136,8 @@ class OathData(Base):
rarity = Column(Text)
position = Column(Text)
Point = Column(Integer)
itemType = Column(Text)
itemDetailType = Column(Text)
categorize = Column(Text)
imageUrl = Column(Text)
detail = Column(Text)
@@ -146,17 +155,21 @@ class OathData(Base):
suit = Column(Text)
wearUrl = Column(Text)
class PrimerData(Base):
"""
誓约属性映射
首字母大写的属性为调适可能会影响到的属性
"""
__tablename__ = 'primer'
id = Column(Integer, primary_key=True)
name = Column(Text)
rarity = Column(Text)
position = Column(Text)
itemType = Column(Text)
itemDetailType = Column(Text)
Point = Column(Integer)
categorize = Column(Text)
imageUrl = Column(Text)
@@ -175,6 +188,7 @@ class PrimerData(Base):
suit = Column(Text)
wearUrl = Column(Text)
class OathSuitData(Base):
__tablename__ = 'oathSuit'
@@ -191,6 +205,7 @@ class OathSuitData(Base):
Attack = Column(Text)
Buffer = Column(Text)
class OathSuitSkillData(Base):
__tablename__ = 'oathSuitSkill'
@@ -205,4 +220,4 @@ class OathSuitSkillData(Base):
Attack = Column(Float)
Buffer = Column(Float)
value = Column(Text)
bufferValue = Column(Text)
bufferValue = Column(Text)
+1 -1
View File
@@ -1,5 +1,5 @@
from database.connect import get_db_engine as get_engine
from database.models import Session, EquData, SuitData
from database.models import Session, EquData
engine = get_engine('0')
+53
View File
@@ -0,0 +1,53 @@
# dcalc Go 重构骨架
这个目录是对现有 Python 后端的并行 Go 重构,不直接替换原服务。
当前已迁移:
- 通用响应结构与 HTTP 入口
- CORS、压缩、中间件基础设施
- 冒险团职业列表与 token 生成
- openapi 静态数据读取接口
- 基于 alter_token 的技能详情接口
- colg 金价查询接口
- /api/character 静态职业与装备目录信息
- /api/skillTree/ 编码解码逻辑(Go 实现)
- Python 适配层(职业工厂依赖收口)
兼容性说明:
- jobInfo 接口保持与 Python 一致,返回目录名风格的 jobId 和 jobGrowId
- skills 和 skillDetail 路由同时兼容目录名参数与 Neople UUID 参数
当前未迁移:
- 角色构建与伤害计算公式本体
- DNFHelper 集成
- MCP 服务
当前架构:
- skillTree 的 base64/zlib/binary 解码已经迁移到 Go
- createCharacter 依赖通过 go/python_bridge.py 适配层收口
- calc 域已拆出 CharacterFactory 与 EquipmentCatalogRepository 边界,便于后续逐步替换 Python 依赖
运行:
```bash
cd go
go run ./cmd/dcalc
```
可选环境变量:
- PORT
- DEBUG_MODE
- COLG_TOKEN
- HELPER_TOKEN
- HELPER_ID
建议迁移顺序:
1. 先把 createCharacter 工厂后的领域模型拆成 Go interface 和 package。
2. 再迁装备/套装/技能数据库读取层。
3. 最后逐职业迁技能公式与计算逻辑。
+52
View File
@@ -0,0 +1,52 @@
package main
import (
"log"
"net/http"
"dcalc-go/internal/config"
"dcalc-go/internal/httpserver"
"dcalc-go/internal/service"
)
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("load config: %v", err)
}
cache := service.NewCache()
adventureService, err := service.NewAdventureService(cfg.ProjectRoot, cache)
if err != nil {
log.Fatalf("load adventure data: %v", err)
}
openAPIService := service.NewOpenAPIService(cfg.ProjectRoot, cache, adventureService)
colgService := service.NewColgService(cache, cfg)
tokenService := service.NewTokenService(adventureService)
characterFactory := service.NewPythonCharacterFactory(cfg)
characterService := service.NewCharacterService(cache, characterFactory)
skillTreeService, err := service.NewSkillTreeService(cfg.ProjectRoot, adventureService, characterFactory)
if err != nil {
log.Fatalf("load skill tree service: %v", err)
}
equipmentRepository := service.NewSQLiteEquipmentCatalogRepository(cfg.ProjectRoot)
calcService := service.NewCalcService(characterFactory, equipmentRepository)
router := httpserver.NewRouter(httpserver.Dependencies{
Config: cfg,
AdventureService: adventureService,
OpenAPIService: openAPIService,
ColgService: colgService,
TokenService: tokenService,
CharacterService: characterService,
SkillTreeService: skillTreeService,
CalcService: calcService,
})
addr := ":" + cfg.Port
log.Printf("dcalc go server listening on %s", addr)
if err := http.ListenAndServe(addr, router); err != nil {
log.Fatalf("listen and serve: %v", err)
}
}
+20
View File
@@ -0,0 +1,20 @@
module dcalc-go
go 1.25.0
require (
github.com/go-chi/chi/v5 v5.2.3
modernc.org/sqlite v1.48.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.42.0 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+53
View File
@@ -0,0 +1,53 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+90
View File
@@ -0,0 +1,90 @@
package config
import (
"fmt"
"os"
"path/filepath"
"strconv"
)
type Config struct {
Port string
DebugMode bool
ColgToken string
HelperToken string
HelperID string
PythonExec string
ProjectRoot string
}
func Load() (Config, error) {
projectRoot, err := detectProjectRoot()
if err != nil {
return Config{}, err
}
return Config{
Port: envOrDefault("PORT", "27173"),
DebugMode: envBoolOrDefault("DEBUG_MODE", true),
ColgToken: os.Getenv("COLG_TOKEN"),
HelperToken: os.Getenv("HELPER_TOKEN"),
HelperID: os.Getenv("HELPER_ID"),
PythonExec: pythonExecOrDefault(projectRoot),
ProjectRoot: projectRoot,
}, nil
}
func pythonExecOrDefault(projectRoot string) string {
if value := os.Getenv("PYTHON_EXEC"); value != "" {
return value
}
venvPython := filepath.Join(projectRoot, ".venv", "bin", "python")
if fileExists(venvPython) {
return venvPython
}
return "python3"
}
func detectProjectRoot() (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
current := wd
for {
if fileExists(filepath.Join(current, "openapi", "jobs.json")) {
return current, nil
}
parent := filepath.Dir(current)
if parent == current {
return "", fmt.Errorf("unable to locate project root from %s", wd)
}
current = parent
}
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func envOrDefault(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
func envBoolOrDefault(key string, fallback bool) bool {
value := os.Getenv(key)
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return fallback
}
return parsed
}
+301
View File
@@ -0,0 +1,301 @@
package httpserver
import (
"encoding/json"
"errors"
"io"
"net/http"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"dcalc-go/internal/config"
"dcalc-go/internal/model"
"dcalc-go/internal/service"
)
type Dependencies struct {
Config config.Config
AdventureService *service.AdventureService
OpenAPIService *service.OpenAPIService
ColgService *service.ColgService
TokenService *service.TokenService
CharacterService *service.CharacterService
SkillTreeService *service.SkillTreeService
CalcService *service.CalcService
}
func NewRouter(dep Dependencies) http.Handler {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Compress(5, "application/json"))
r.Use(cors)
r.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, model.Success(map[string]any{"service": "dcalc-go", "debug": dep.Config.DebugMode}))
})
r.Route("/api", func(r chi.Router) {
r.Get("/adventure", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.AdventureService.AdventureList()
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/token/get/{alter}", func(w http.ResponseWriter, r *http.Request) {
token, err := dep.TokenService.Create(chi.URLParam(r, "alter"), r.URL.Query().Get("equVersion"))
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(400, err.Error(), nil))
return
}
writeJSON(w, http.StatusOK, model.Success(token))
})
r.Get("/character", func(w http.ResponseWriter, r *http.Request) {
payload, ok := requireAlterToken(w, r, dep.TokenService)
if !ok {
return
}
data, err := dep.CharacterService.StaticInfo(payload.Alter, payload.EquVersion)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Post("/calc", func(w http.ResponseWriter, r *http.Request) {
payload, ok := requireAlterToken(w, r, dep.TokenService)
if !ok {
return
}
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, http.ErrBodyNotAllowed) {
writeJSON(w, http.StatusBadRequest, model.Error(400, "invalid request body", nil))
return
}
data, err := dep.CalcService.Calculate(payload.Alter, payload.EquVersion, body)
if err != nil {
writeNotImplemented(w, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/skill/{skillId}/{level}", func(w http.ResponseWriter, r *http.Request) {
payload, ok := requireAlterToken(w, r, dep.TokenService)
if !ok {
return
}
level, err := strconv.Atoi(chi.URLParam(r, "level"))
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(400, "level must be an integer", nil))
return
}
data, err := dep.OpenAPIService.SkillDetailByAlter(payload.Alter, chi.URLParam(r, "skillId"), level)
if err != nil {
writeJSON(w, http.StatusNotFound, model.Error(404, err.Error(), nil))
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/dnfhelper/{uid}/", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusNotImplemented, model.Error(501, "dnfhelper integration is pending migration", nil))
})
r.Post("/skillTree/", func(w http.ResponseWriter, r *http.Request) {
payload, ok := requireAlterToken(w, r, dep.TokenService)
if !ok {
return
}
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil && !errors.Is(err, io.EOF) {
writeJSON(w, http.StatusBadRequest, model.Error(400, "invalid request body", nil))
return
}
code, _ := body["code"].(string)
result, err := dep.SkillTreeService.Parse(code, payload.EquVersion)
if err != nil {
writeJSON(w, http.StatusInternalServerError, model.Error(500, "解析技能加点失败", nil))
return
}
if result.Job["class"] != payload.Alter {
writeJSON(w, http.StatusInternalServerError, model.Error(500, "技能加点代码职业与登录职业不匹配", nil))
return
}
writeJSON(w, http.StatusOK, model.Success(result))
})
r.Route("/open", func(r chi.Router) {
r.Get("/skills/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.OpenAPIService.SearchSkills(r.URL.Query().Get("skillName"))
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/jobInfo/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.OpenAPIService.JobsInfo(r.URL.Query().Get("jobName"))
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/skillData/summary/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.OpenAPIService.SkillDataSummary(
r.URL.Query().Get("job"),
splitCSV(r.URL.Query().Get("skills")),
splitCSV(r.URL.Query().Get("weapons")),
)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/gold/realtime/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.ColgService.RealTimeGold(r.Context())
if err != nil {
writeError(w, http.StatusBadGateway, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/gold/history/", func(w http.ResponseWriter, r *http.Request) {
region, err := strconv.Atoi(r.URL.Query().Get("region"))
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(400, "region must be an integer", nil))
return
}
timeRange := 7
if raw := r.URL.Query().Get("timeRange"); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(400, "timeRange must be an integer", nil))
return
}
timeRange = parsed
}
data, err := dep.ColgService.HistoryGold(r.Context(), region, timeRange)
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(404, err.Error(), nil))
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/gold/official/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.ColgService.OfficialGold(r.Context())
if err != nil {
writeError(w, http.StatusBadGateway, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/{jobId}/{jobGrowId}/skills/", func(w http.ResponseWriter, r *http.Request) {
data, err := dep.OpenAPIService.SkillsByJob(chi.URLParam(r, "jobId"), chi.URLParam(r, "jobGrowId"))
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
r.Get("/{jobId}/{jobGrowId}/{skillId}/", func(w http.ResponseWriter, r *http.Request) {
var level *int
if raw := r.URL.Query().Get("level"); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil {
writeJSON(w, http.StatusBadRequest, model.Error(400, "level must be an integer", nil))
return
}
level = &parsed
}
data, err := dep.OpenAPIService.SkillDetail(chi.URLParam(r, "jobId"), chi.URLParam(r, "jobGrowId"), chi.URLParam(r, "skillId"), level)
if err != nil {
writeJSON(w, http.StatusNotFound, model.Error(404, err.Error(), nil))
return
}
writeJSON(w, http.StatusOK, model.Success(data))
})
})
})
return r
}
func requireAlterToken(w http.ResponseWriter, r *http.Request, tokens *service.TokenService) (service.AlterTokenPayload, bool) {
token := r.Header.Get("alter_token")
if token == "" {
writeJSON(w, http.StatusUnauthorized, model.Error(401, "missing alter_token header", nil))
return service.AlterTokenPayload{}, false
}
payload, err := tokens.Parse(token)
if err != nil {
writeJSON(w, http.StatusUnauthorized, model.Error(401, "invalid alter_token header", nil))
return service.AlterTokenPayload{}, false
}
return payload, true
}
func writeNotImplemented(w http.ResponseWriter, err error) {
writeJSON(w, http.StatusNotImplemented, model.Error(501, err.Error(), nil))
}
func writeError(w http.ResponseWriter, status int, err error) {
writeJSON(w, status, model.Error(status, err.Error(), nil))
}
func writeJSON(w http.ResponseWriter, status int, payload model.Envelope) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, alter_token")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func splitCSV(value string) []string {
if value == "" {
return nil
}
parts := strings.Split(value, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
+15
View File
@@ -0,0 +1,15 @@
package model
type Envelope struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data"`
}
func Success(data any) Envelope {
return Envelope{Code: 200, Message: "Success", Data: data}
}
func Error(code int, message string, data any) Envelope {
return Envelope{Code: code, Message: message, Data: data}
}
+137
View File
@@ -0,0 +1,137 @@
package service
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
type AdventureService struct {
projectRoot string
cache *Cache
jobs []Job
tokenToJob map[string]string
}
type Job struct {
Name string `json:"name"`
JobID string `json:"jobId"`
Title string `json:"title"`
Children []JobChild `json:"children"`
}
type JobChild struct {
Name string `json:"name"`
JobGrowID string `json:"jobGrowId"`
Title string `json:"title"`
}
type AdventureParent struct {
ID int `json:"id"`
Name string `json:"name"`
Title string `json:"title"`
Children []AdventureChild `json:"children"`
}
type AdventureChild struct {
ID int `json:"id"`
Name string `json:"name"`
Title string `json:"title"`
Open bool `json:"open"`
Value string `json:"value"`
}
func NewAdventureService(projectRoot string, cache *Cache) (*AdventureService, error) {
jobsPath := filepath.Join(projectRoot, "openapi", "jobs.json")
content, err := os.ReadFile(jobsPath)
if err != nil {
return nil, err
}
var jobs []Job
if err := json.Unmarshal(content, &jobs); err != nil {
return nil, err
}
svc := &AdventureService{
projectRoot: projectRoot,
cache: cache,
jobs: jobs,
tokenToJob: make(map[string]string),
}
for parentIndex, parent := range jobs {
for childIndex, child := range parent.Children {
token := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%02d%02d00", parentIndex, childIndex)))
svc.tokenToJob[token] = "GF." + parent.Name + "." + child.Name
}
}
return svc, nil
}
func (s *AdventureService) Jobs() []Job {
jobs := make([]Job, len(s.jobs))
copy(jobs, s.jobs)
return jobs
}
func (s *AdventureService) AdventureList() ([]AdventureParent, error) {
return GetOrLoad(s.cache, "adventure:list", 10*time.Minute, func() ([]AdventureParent, error) {
result := make([]AdventureParent, 0, len(s.jobs))
for parentIndex, parent := range s.jobs {
children := make([]AdventureChild, 0, len(parent.Children))
for childIndex, child := range parent.Children {
children = append(children, AdventureChild{
ID: childIndex,
Name: child.Name,
Title: child.Title,
Open: true,
Value: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%02d%02d00", parentIndex, childIndex))),
})
}
result = append(result, AdventureParent{
ID: parentIndex,
Name: parent.Name,
Title: parent.Title,
Children: children,
})
}
return result, nil
})
}
func (s *AdventureService) AlterByToken(token string) (string, error) {
alter, ok := s.tokenToJob[token]
if !ok {
return "", fmt.Errorf("invalid adventure token")
}
return alter, nil
}
func (s *AdventureService) ResolveDirectories(jobIdentifier string, jobGrowIdentifier string) (string, string, error) {
for _, job := range s.jobs {
jobMatched := jobIdentifier == job.Name || jobIdentifier == job.JobID
if !jobMatched {
continue
}
for _, child := range job.Children {
if jobGrowIdentifier == child.Name || jobGrowIdentifier == child.JobGrowID {
return job.Name, child.Name, nil
}
}
}
return "", "", fmt.Errorf("unknown job path: %s/%s", jobIdentifier, jobGrowIdentifier)
}
func (s *AdventureService) DirectoriesFromAlter(alter string) (string, string, error) {
parts := strings.Split(alter, ".")
if len(parts) != 3 || parts[0] != "GF" {
return "", "", fmt.Errorf("invalid alter value: %s", alter)
}
return parts[1], parts[2], nil
}
+44
View File
@@ -0,0 +1,44 @@
package service
import (
"sync"
"time"
)
type Cache struct {
mu sync.RWMutex
items map[string]cacheItem
}
type cacheItem struct {
value any
expiresAt time.Time
}
func NewCache() *Cache {
return &Cache{items: make(map[string]cacheItem)}
}
func GetOrLoad[T any](cache *Cache, key string, ttl time.Duration, loader func() (T, error)) (T, error) {
var zero T
cache.mu.RLock()
item, ok := cache.items[key]
cache.mu.RUnlock()
if ok && (item.expiresAt.IsZero() || item.expiresAt.After(time.Now())) {
if value, castOK := item.value.(T); castOK {
return value, nil
}
}
value, err := loader()
if err != nil {
return zero, err
}
cache.mu.Lock()
cache.items[key] = cacheItem{value: value, expiresAt: time.Now().Add(ttl)}
cache.mu.Unlock()
return value, nil
}
+34
View File
@@ -0,0 +1,34 @@
package service
import "errors"
var ErrNotImplemented = errors.New("calculation domain has not been migrated to Go yet")
type EquipmentCatalogRepository interface {
Catalog(alter string, equVersion string) (map[string]any, error)
}
type CalcService struct {
factory CharacterFactory
equipment EquipmentCatalogRepository
}
func NewCalcService(factory CharacterFactory, equipment EquipmentCatalogRepository) *CalcService {
return &CalcService{factory: factory, equipment: equipment}
}
func (s *CalcService) CharacterInfo(alter string, equVersion string) (any, error) {
return s.factory.CharacterInfo(alter, equVersion)
}
func (s *CalcService) Calculate(_ string, _ string, _ map[string]any) (any, error) {
return nil, ErrNotImplemented
}
func (s *CalcService) SkillTree(_ string, _ string) (any, error) {
return nil, ErrNotImplemented
}
func (s *CalcService) DNFHelper(_ string, _ string) (any, error) {
return nil, ErrNotImplemented
}
+21
View File
@@ -0,0 +1,21 @@
package service
import (
"time"
)
type CharacterService struct {
cache *Cache
factory CharacterFactory
}
func NewCharacterService(cache *Cache, factory CharacterFactory) *CharacterService {
return &CharacterService{cache: cache, factory: factory}
}
func (s *CharacterService) StaticInfo(alter string, equVersion string) (map[string]any, error) {
key := "character:static:" + alter + ":" + equVersion
return GetOrLoad(s.cache, key, 15*time.Minute, func() (map[string]any, error) {
return s.factory.CharacterInfo(alter, equVersion)
})
}
+304
View File
@@ -0,0 +1,304 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"dcalc-go/internal/config"
)
type ColgService struct {
cache *Cache
config config.Config
client *http.Client
}
var regionMap = map[string]int{
"跨1": 1,
"跨2": 2,
"跨3A": 3,
"跨3B": 4,
"跨4": 5,
"跨5": 6,
"跨6": 7,
"跨7": 8,
}
func NewColgService(cache *Cache, cfg config.Config) *ColgService {
return &ColgService{
cache: cache,
config: cfg,
client: &http.Client{Timeout: 12 * time.Second},
}
}
func (s *ColgService) RealTimeGold(ctx context.Context) ([]map[string]any, error) {
if s.config.ColgToken == "" {
return nil, fmt.Errorf("COLG_TOKEN is required")
}
return GetOrLoad(s.cache, "colg:realtime", 30*time.Minute, func() ([]map[string]any, error) {
result := make([]map[string]any, 0, len(regionMap))
now := time.Now().Format("2006-01-02")
for regionName, regionID := range regionMap {
data, err := s.requestTrendData(ctx, regionID, now, now)
if err != nil {
return nil, err
}
xValues, _ := data["x"].([]any)
yValues, _ := data["y"].([]any)
if len(xValues) == 0 || len(yValues) == 0 {
continue
}
result = append(result, map[string]any{
"updataTime": formatBeijingTime(int64FromAny(xValues[len(xValues)-1])),
"region": regionName,
"goldPrice": float64FromAny(yValues[len(yValues)-1]),
})
}
return result, nil
})
}
func (s *ColgService) HistoryGold(ctx context.Context, region int, timeRange int) (map[string]any, error) {
if s.config.ColgToken == "" {
return nil, fmt.Errorf("COLG_TOKEN is required")
}
if timeRange <= 0 {
timeRange = 7
}
regionName := ""
for name, id := range regionMap {
if id == region {
regionName = name
break
}
}
if regionName == "" {
return nil, fmt.Errorf("invalid region")
}
endDate := time.Now()
startDate := endDate.AddDate(0, 0, -(timeRange - 1))
trend, err := s.requestTrendData(ctx, region, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"))
if err != nil {
return nil, err
}
xValues, _ := trend["x"].([]any)
yValues, _ := trend["y"].([]any)
y1Values, _ := trend["y1"].([]any)
rows := make([]map[string]any, 0, len(xValues))
minValue := 0.0
maxValue := 0.0
for index := range xValues {
avg := float64FromAny(yValues[index])
zero := float64FromAny(y1Values[index])
if index == 0 || avg < minValue {
minValue = avg
}
if zero < minValue {
minValue = zero
}
if index == 0 || avg > maxValue {
maxValue = avg
}
if zero > maxValue {
maxValue = zero
}
rows = append(rows, map[string]any{
"date": strings.Split(formatBeijingTime(int64FromAny(xValues[index])), " ")[0],
"avg": avg,
"zero": zero,
})
}
title := fmt.Sprintf("DNF%s近%d天金币价格趋势分析", regionName, timeRange)
echarts := map[string]any{
"title": map[string]any{"text": title, "left": "center"},
"toolbox": map[string]any{"feature": map[string]any{"saveAsImage": map[string]any{}, "dataView": map[string]any{"readOnly": true}, "magicType": map[string]any{"type": []string{"line", "bar"}}}},
"tooltip": map[string]any{"trigger": "axis", "axisPointer": map[string]any{"type": "cross", "label": map[string]any{"backgroundColor": "#6a7985"}}},
"legend": map[string]any{"data": []string{"平均金价", "12时金价"}, "bottom": 0},
"xAxis": map[string]any{"type": "category", "name": "日期", "boundaryGap": false, "data": extractField(rows, "date")},
"yAxis": map[string]any{"type": "value", "name": "价格 (万金币/1人民币)", "min": floorToFive(minValue), "max": floorToFive(maxValue) + 5, "axisLabel": map[string]any{"formatter": "{value} 万金币"}},
"series": []map[string]any{
{"name": "平均金价", "type": "line", "smooth": true, "data": extractField(rows, "avg"), "markPoint": map[string]any{"data": []map[string]any{{"type": "max", "name": "最大值"}, {"type": "min", "name": "最小值"}}}},
{"name": "12时金价", "type": "line", "smooth": true, "data": extractField(rows, "zero"), "markLine": map[string]any{"data": []map[string]any{{"type": "average", "name": "平均值"}}}},
},
}
return map[string]any{"region": regionName, "data": rows, "echartsData": echarts}, nil
}
func (s *ColgService) OfficialGold(ctx context.Context) ([]map[string]any, error) {
if s.config.HelperToken == "" || s.config.HelperID == "" {
return nil, fmt.Errorf("HELPER_TOKEN and HELPER_ID are required")
}
return GetOrLoad(s.cache, "colg:official", 30*time.Minute, func() ([]map[string]any, error) {
servers := []string{"1", "2", "3A", "3B", "4", "5", "6", "7"}
result := make([]map[string]any, 0, len(servers))
for _, server := range servers {
data, err := s.requestOfficialGold(ctx, server)
if err != nil {
return nil, err
}
result = append(result, data)
}
return result, nil
})
}
func (s *ColgService) requestTrendData(ctx context.Context, region int, startTime string, endTime string) (map[string]any, error) {
payload := map[string]any{
"token": s.config.ColgToken,
"area": region,
"time_type": "other",
"start_time": startTime,
"end_time": endTime,
}
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://poservice.colg.cn/api/v1/trend/getCoinTrendDataKol", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var payloadResp map[string]any
if err := json.Unmarshal(content, &payloadResp); err != nil {
return nil, err
}
data, _ := payloadResp["data"].(map[string]any)
return data, nil
}
func (s *ColgService) requestOfficialGold(ctx context.Context, server string) (map[string]any, error) {
form := url.Values{}
form.Set("r", "godRank/godDealRank")
form.Set("userId", s.config.HelperID)
form.Set("token", s.config.HelperToken)
form.Set("serverNo", server)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://dzhu.qq.com/zangyi/activity/app", strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var payloadResp map[string]any
if err := json.Unmarshal(content, &payloadResp); err != nil {
return nil, err
}
data, _ := payloadResp["data"].(map[string]any)
return map[string]any{
"updataTime": time.Now().Format("2006-01-02 15:04:05"),
"region": "跨" + server,
"goldDealRank": extractPrices(data["godDealRank"]),
"goldTopDealRank": extractPrices(data["godTopDealRank"]),
}, nil
}
func extractPrices(value any) []int {
rows, _ := value.([]any)
result := make([]int, 0, len(rows))
for _, row := range rows {
rowMap, _ := row.(map[string]any)
result = append(result, intFromAnyString(rowMap["price"]))
}
return result
}
func extractField(rows []map[string]any, key string) []any {
result := make([]any, 0, len(rows))
for _, row := range rows {
result = append(result, row[key])
}
return result
}
func formatBeijingTime(ts int64) string {
return time.Unix(ts, 0).UTC().Add(8 * time.Hour).Format("2006-01-02 15:04:05")
}
func floorToFive(value float64) float64 {
return float64(int(value/5) * 5)
}
func float64FromAny(value any) float64 {
switch typed := value.(type) {
case float64:
return typed
case float32:
return float64(typed)
case int:
return float64(typed)
case int64:
return float64(typed)
case json.Number:
parsed, _ := typed.Float64()
return parsed
default:
return 0
}
}
func int64FromAny(value any) int64 {
switch typed := value.(type) {
case int64:
return typed
case int:
return int64(typed)
case float64:
return int64(typed)
case json.Number:
parsed, _ := typed.Int64()
return parsed
default:
return 0
}
}
func intFromAnyString(value any) int {
switch typed := value.(type) {
case string:
parsed, _ := strconv.Atoi(typed)
return parsed
default:
return int(float64FromAny(value))
}
}
@@ -0,0 +1,295 @@
package service
import (
"database/sql"
"fmt"
"path/filepath"
"strconv"
"strings"
_ "modernc.org/sqlite"
)
type SQLiteEquipmentCatalogRepository struct {
projectRoot string
}
func NewSQLiteEquipmentCatalogRepository(projectRoot string) *SQLiteEquipmentCatalogRepository {
return &SQLiteEquipmentCatalogRepository{projectRoot: projectRoot}
}
func (r *SQLiteEquipmentCatalogRepository) Catalog(_ string, equVersion string) (map[string]any, error) {
if equVersion == "" {
equVersion = "0"
}
dbPath := filepath.Join(r.projectRoot, "dataFiles", fmt.Sprintf("dcalc_%s.db", equVersion))
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}
defer db.Close()
equips, err := queryEquipRows(db, "equ")
if err != nil {
return nil, err
}
stones, err := queryEquipRows(db, "stone")
if err != nil {
return nil, err
}
suits, err := querySuitRows(db)
if err != nil {
return nil, err
}
enchants, err := queryEnchantRows(db, "enchant")
if err != nil {
return nil, err
}
emblems, err := queryEnchantRows(db, "emblem")
if err != nil {
return nil, err
}
jades, err := queryJadeRows(db)
if err != nil {
return nil, err
}
return map[string]any{
"equips": equips,
"suits": suits,
"stones": stones,
"enchants": enchants,
"emblems": emblems,
"jades": jades,
"avatar": map[string]any{},
"options": map[string]any{},
"sundry": []any{},
}, nil
}
func queryEquipRows(db *sql.DB, table string) ([]map[string]any, error) {
query := fmt.Sprintf("SELECT id,name,rarity,itemType,itemDetailType,Point,categorize,imageUrl,detail,bufferDetail,STR,INT,Vitality,Spirit,AtkP,AtkM,AtkI,SkillAttack,Attack,Buffer,suit FROM %s", table)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]map[string]any, 0)
for rows.Next() {
var (
id, name, rarity, itemType, itemDetailType, point, categorize, imageURL, detail, bufferDetail sql.NullString
strV, intV, vitV, spiV, atkPV, atkMV, atkIV, skillAttackV, attackV, bufferV, suitV sql.NullString
)
if err := rows.Scan(&id, &name, &rarity, &itemType, &itemDetailType, &point, &categorize, &imageURL, &detail, &bufferDetail, &strV, &intV, &vitV, &spiV, &atkPV, &atkMV, &atkIV, &skillAttackV, &attackV, &bufferV, &suitV); err != nil {
return nil, err
}
entry := map[string]any{
"id": nullString(id),
"name": nullString(name),
"rarity": nullString(rarity),
"itemType": nullString(itemType),
"itemDetailType": nullString(itemDetailType),
"categorize": nullString(categorize),
"imageUrl": nullString(imageURL),
"detail": nullString(detail),
"bufferDetail": nullString(bufferDetail),
"Point": numberListOrDefault(nullString(point)),
"STR": numberListOrDefault(nullString(strV)),
"INT": numberListOrDefault(nullString(intV)),
"Vitality": numberListOrDefault(nullString(vitV)),
"Spirit": numberListOrDefault(nullString(spiV)),
"AtkP": numberListOrDefault(nullString(atkPV)),
"AtkM": numberListOrDefault(nullString(atkMV)),
"AtkI": numberListOrDefault(nullString(atkIV)),
"SkillAttack": numberListOrDefault(nullString(skillAttackV)),
"Attack": numberListOrDefault(nullString(attackV)),
"Buffer": numberListOrDefault(nullString(bufferV)),
"suit": suitListOrDefault(nullString(suitV)),
}
maxAdaptation := 0
for _, key := range []string{"Point", "STR", "INT", "Vitality", "Spirit", "AtkP", "AtkM", "AtkI", "SkillAttack", "Attack", "Buffer"} {
if values, ok := entry[key].([]float64); ok && len(values)-1 > maxAdaptation {
maxAdaptation = len(values) - 1
}
}
entry["max_adaptation"] = maxAdaptation
result = append(result, entry)
}
return result, rows.Err()
}
func querySuitRows(db *sql.DB) ([]map[string]any, error) {
rows, err := db.Query("SELECT id,suitId,suitName,rarity,name,point,level,count,SkillAttack,Attack,Buffer,imageUrl,value,bufferValue,fame FROM suit")
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]map[string]any, 0)
for rows.Next() {
var (
id, suitID, point, level, count, fame sql.NullInt64
suitName, rarity, name, imageURL, value, bufferValue sql.NullString
skillAttack, attack, buffer sql.NullFloat64
)
if err := rows.Scan(&id, &suitID, &suitName, &rarity, &name, &point, &level, &count, &skillAttack, &attack, &buffer, &imageURL, &value, &bufferValue, &fame); err != nil {
return nil, err
}
result = append(result, map[string]any{
"id": nullInt64(id),
"suitId": nullInt64(suitID),
"suitName": nullString(suitName),
"rarity": nullString(rarity),
"name": nullString(name),
"point": nullInt64(point),
"level": nullInt64(level),
"count": nullInt64(count),
"SkillAttack": nullFloat64(skillAttack),
"Attack": nullFloat64(attack),
"Buffer": nullFloat64(buffer),
"imageUrl": nullString(imageURL),
"value": nullString(value),
"bufferValue": nullString(bufferValue),
"fame": nullInt64(fame),
})
}
return result, rows.Err()
}
func queryEnchantRows(db *sql.DB, table string) ([]map[string]any, error) {
query := fmt.Sprintf("SELECT id,detail,categorize,itemType,fame,rarity FROM %s", table)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]map[string]any, 0)
for rows.Next() {
var id, fame sql.NullInt64
var detail, categorize, itemType, rarity sql.NullString
if err := rows.Scan(&id, &detail, &categorize, &itemType, &fame, &rarity); err != nil {
return nil, err
}
result = append(result, map[string]any{
"id": nullInt64(id),
"detail": nullString(detail),
"categorize": splitCSV(nullString(categorize)),
"position": splitCSV(nullString(itemType)),
"fame": nullInt64(fame),
"rarity": nullString(rarity),
})
}
return result, rows.Err()
}
func queryJadeRows(db *sql.DB) ([]map[string]any, error) {
rows, err := db.Query("SELECT id,name,min,max,unit,pre FROM jade")
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]map[string]any, 0)
for rows.Next() {
var id, minV, maxV sql.NullInt64
var name, unit sql.NullString
var pre sql.NullFloat64
if err := rows.Scan(&id, &name, &minV, &maxV, &unit, &pre); err != nil {
return nil, err
}
result = append(result, map[string]any{
"id": nullInt64(id),
"name": nullString(name),
"min": nullInt64(minV),
"max": nullInt64(maxV),
"unit": nullString(unit),
"pre": nullFloat64(pre),
})
}
return result, rows.Err()
}
func numberListOrDefault(raw string) []float64 {
raw = strings.TrimSpace(raw)
if raw == "" {
return []float64{0}
}
parts := strings.Split(raw, ",")
result := make([]float64, 0, len(parts))
for _, part := range parts {
value, err := strconv.ParseFloat(strings.TrimSpace(part), 64)
if err != nil {
result = append(result, 0)
continue
}
result = append(result, value)
}
if len(result) == 0 {
return []float64{0}
}
return result
}
func suitListOrDefault(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" {
return []string{}
}
parts := strings.Split(raw, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
value, err := strconv.ParseFloat(part, 64)
if err != nil {
result = append(result, part)
continue
}
result = append(result, strconv.Itoa(int(value)))
}
return result
}
func splitCSV(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" {
return []string{}
}
parts := strings.Split(raw, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
result = append(result, part)
}
}
return result
}
func nullString(value sql.NullString) string {
if !value.Valid {
return ""
}
return value.String
}
func nullInt64(value sql.NullInt64) int64 {
if !value.Valid {
return 0
}
return value.Int64
}
func nullFloat64(value sql.NullFloat64) float64 {
if !value.Valid {
return 0
}
return value.Float64
}
+264
View File
@@ -0,0 +1,264 @@
package service
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
var placeholderPattern = regexp.MustCompile(`<(?:(?:int)|(?:float\d*))>`)
var filenameJobPrefix = regexp.MustCompile(`^\d+(?:-\d+){0,2}`)
type OpenAPIService struct {
projectRoot string
cache *Cache
adventure *AdventureService
}
func NewOpenAPIService(projectRoot string, cache *Cache, adventure *AdventureService) *OpenAPIService {
return &OpenAPIService{projectRoot: projectRoot, cache: cache, adventure: adventure}
}
func (s *OpenAPIService) SearchSkills(skillName string) ([]map[string]any, error) {
skills, err := GetOrLoad(s.cache, "openapi:skills", 30*time.Minute, func() ([]map[string]any, error) {
var data []map[string]any
if err := s.readJSON(filepath.Join(s.projectRoot, "openapi", "data", "skills.json"), &data); err != nil {
return nil, err
}
return data, nil
})
if err != nil {
return nil, err
}
normalized := normalizeWhitespace(skillName)
if normalized == "" {
return skills, nil
}
filtered := make([]map[string]any, 0)
for _, skill := range skills {
name, _ := skill["skillName"].(string)
if strings.Contains(normalizeWhitespace(name), normalized) {
filtered = append(filtered, skill)
}
}
return filtered, nil
}
func (s *OpenAPIService) JobsInfo(jobName string) ([]map[string]string, error) {
jobName = strings.NewReplacer("(", "(", ")", ")").Replace(jobName)
result := make([]map[string]string, 0)
for _, job := range s.adventure.Jobs() {
for _, child := range job.Children {
if jobName == "" || strings.Contains(child.Title, jobName) || strings.Contains(child.Name, jobName) {
result = append(result, map[string]string{
"jobId": job.Name,
"jobName": job.Title,
"jobGrowId": child.Name,
"jobGrowName": child.Title,
})
}
}
}
return result, nil
}
func (s *OpenAPIService) SkillsByJob(jobID string, jobGrowID string) (any, error) {
jobDir, jobGrowDir, err := s.adventure.ResolveDirectories(jobID, jobGrowID)
if err != nil {
return nil, err
}
var data any
path := filepath.Join(s.projectRoot, "openapi", "data", jobDir, jobGrowDir, "cn", "skill_tree.json")
if err := s.readJSON(path, &data); err != nil {
return nil, err
}
return data, nil
}
func (s *OpenAPIService) SkillDetail(jobID string, jobGrowID string, skillID string, level *int) (any, error) {
jobDir, jobGrowDir, err := s.adventure.ResolveDirectories(jobID, jobGrowID)
if err != nil {
return nil, err
}
var skillInfo map[string]any
path := filepath.Join(s.projectRoot, "openapi", "data", jobDir, jobGrowDir, "cn", "skillDetail", skillID+".json")
if err := s.readJSON(path, &skillInfo); err != nil {
return nil, err
}
if levelInfo, ok := skillInfo["levelInfo"].(map[string]any); ok {
if optionDesc, ok := levelInfo["optionDesc"].(string); ok {
levelInfo["optionDesc"] = replacePlaceholders(optionDesc)
}
}
if level == nil {
return skillInfo, nil
}
maxLevel := intFromAny(skillInfo["maxLevel"])
bounded := *level
if bounded < 0 {
bounded = 0
}
if maxLevel > 0 && bounded > maxLevel {
bounded = maxLevel
}
levelInfo, ok := skillInfo["levelInfo"].(map[string]any)
if !ok {
return skillInfo, nil
}
rows, ok := levelInfo["rows"].([]any)
if !ok {
return skillInfo, nil
}
for _, row := range rows {
rowMap, ok := row.(map[string]any)
if !ok || intFromAny(rowMap["level"]) != bounded {
continue
}
for key, value := range rowMap {
levelInfo[key] = value
}
detail, _ := levelInfo["optionDesc"].(string)
if optionValue, ok := levelInfo["optionValue"].(map[string]any); ok {
for key, value := range optionValue {
detail = strings.ReplaceAll(detail, "{"+key+"}", fmt.Sprint(value))
}
}
levelInfo["detail"] = detail
delete(levelInfo, "rows")
skillInfo["attribute"] = levelInfo
delete(skillInfo, "levelInfo")
break
}
return skillInfo, nil
}
func (s *OpenAPIService) SkillDetailByAlter(alter string, skillID string, level int) (any, error) {
jobDir, jobGrowDir, err := s.adventure.DirectoriesFromAlter(alter)
if err != nil {
return nil, err
}
return s.SkillDetail(jobDir, jobGrowDir, skillID, &level)
}
func (s *OpenAPIService) SkillDataSummary(job string, skills []string, weapons []string) ([]map[string]any, error) {
job = strings.NewReplacer("(", "(", ")", ")").Replace(job)
entries, err := os.ReadDir(filepath.Join(s.projectRoot, "openapi", "summary"))
if err != nil {
return nil, err
}
skillFilter := make(map[string]struct{})
for _, skill := range skills {
if skill != "" {
skillFilter[skill] = struct{}{}
}
}
weaponFilter := make(map[string]struct{})
for _, weapon := range weapons {
if weapon != "" {
weaponFilter[weapon] = struct{}{}
}
}
result := make([]map[string]any, 0)
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") || !strings.Contains(entry.Name(), job) {
continue
}
parts := strings.Split(strings.TrimSuffix(entry.Name(), ".json"), "_")
weapon := "通用"
if len(parts) > 1 && parts[len(parts)-1] != "" {
weapon = parts[len(parts)-1]
}
if len(weaponFilter) > 0 {
if _, ok := weaponFilter[weapon]; !ok {
continue
}
}
var rows []map[string]any
if err := s.readJSON(filepath.Join(s.projectRoot, "openapi", "summary", entry.Name()), &rows); err != nil {
return nil, err
}
jobTitle := strings.TrimPrefix(parts[0], filenameJobPrefix.FindString(parts[0]))
for _, row := range rows {
if len(skillFilter) > 0 {
skillName, _ := row["技能名称"].(string)
if _, ok := skillFilter[skillName]; !ok {
continue
}
}
copied := cloneMap(row)
copied["武器类型"] = weapon
copied["职业"] = jobTitle
result = append(result, copied)
}
}
return result, nil
}
func (s *OpenAPIService) readJSON(path string, dest any) error {
content, err := os.ReadFile(path)
if err != nil {
return err
}
if err := json.Unmarshal(content, dest); err != nil {
return fmt.Errorf("unmarshal %s: %w", path, err)
}
return nil
}
func replacePlaceholders(template string) string {
count := 0
return placeholderPattern.ReplaceAllStringFunc(template, func(_ string) string {
count++
return fmt.Sprintf("{value%d}", count)
})
}
func normalizeWhitespace(value string) string {
return strings.Join(strings.Fields(value), "")
}
func intFromAny(value any) int {
switch typed := value.(type) {
case int:
return typed
case int32:
return int(typed)
case int64:
return int(typed)
case float64:
return int(typed)
case float32:
return int(typed)
default:
return 0
}
}
func cloneMap(source map[string]any) map[string]any {
cloned := make(map[string]any, len(source))
for key, value := range source {
cloned[key] = value
}
return cloned
}
@@ -0,0 +1,83 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"time"
"dcalc-go/internal/config"
)
type CharacterSkillMeta struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
LearnLv int `json:"learnLv"`
MaxLearnLv int `json:"maxLearnLv"`
HasVP bool `json:"hasVP"`
HasUP bool `json:"hasUP"`
UUID string `json:"uuid"`
}
type CharacterFactory interface {
CharacterInfo(alter string, equVersion string) (map[string]any, error)
CharacterSkills(alter string, equVersion string) ([]CharacterSkillMeta, error)
}
type PythonCharacterFactory struct {
pythonExec string
projectRoot string
scriptPath string
}
func NewPythonCharacterFactory(cfg config.Config) *PythonCharacterFactory {
return &PythonCharacterFactory{
pythonExec: cfg.PythonExec,
projectRoot: cfg.ProjectRoot,
scriptPath: filepath.Join(cfg.ProjectRoot, "go", "python_bridge.py"),
}
}
func (p *PythonCharacterFactory) CharacterInfo(alter string, equVersion string) (map[string]any, error) {
result := map[string]any{}
if err := p.invoke("character-info", alter, equVersion, &result); err != nil {
return nil, err
}
return result, nil
}
func (p *PythonCharacterFactory) CharacterSkills(alter string, equVersion string) ([]CharacterSkillMeta, error) {
var result []CharacterSkillMeta
if err := p.invoke("character-skills", alter, equVersion, &result); err != nil {
return nil, err
}
return result, nil
}
func (p *PythonCharacterFactory) invoke(command string, alter string, equVersion string, out any) error {
if equVersion == "" {
equVersion = "0"
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, p.pythonExec, p.scriptPath, p.projectRoot, command, alter, equVersion)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("python bridge failed: %w, stdout=%s, stderr=%s", err, stdout.String(), stderr.String())
}
if err := json.Unmarshal(stdout.Bytes(), out); err != nil {
return fmt.Errorf("unmarshal python bridge output: %w, raw=%s", err, stdout.String())
}
return nil
}
+376
View File
@@ -0,0 +1,376 @@
package service
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"unicode/utf16"
)
type SkillTreeService struct {
adventure *AdventureService
factory CharacterFactory
indexToUID map[int][]string
}
type decodedSkillCode struct {
Header skillCodeHeader
Skills []skillLearnItem
Evolutions []skillChoice
Enhancements []skillChoice
}
type skillCodeHeader struct {
Name string
Level int
JobBytes [2]int
}
type skillLearnItem struct {
SkillIndex int
Level int
SkillType int
}
type skillChoice struct {
SkillIndex int
Type int
}
type LearnedSkill struct {
Lv int `json:"lv"`
Up int `json:"up"`
VP int `json:"vp"`
Name string `json:"name"`
}
type SkillTreeResult struct {
Name string `json:"name"`
Level int `json:"level"`
Job map[string]string `json:"job"`
Skills map[string]LearnedSkill `json:"skills"`
}
var roleMap = map[int]int{
0: 0,
1: 3,
2: 4,
3: 7,
4: 8,
5: 5,
6: 10,
7: 2,
8: 6,
9: 15,
10: 15,
11: 1,
12: 11,
13: 12,
14: 9,
15: 13,
16: 14,
}
func NewSkillTreeService(projectRoot string, adventure *AdventureService, factory CharacterFactory) (*SkillTreeService, error) {
indexToUID, err := loadSkillIndexMap(filepath.Join(projectRoot, "dataFiles", "skillMap.json"))
if err != nil {
return nil, err
}
return &SkillTreeService{adventure: adventure, factory: factory, indexToUID: indexToUID}, nil
}
func (s *SkillTreeService) Parse(code string, equVersion string) (SkillTreeResult, error) {
decoded, err := decodeSkillCode(code)
if err != nil {
return SkillTreeResult{}, err
}
alter, err := alterFromJobBytes(decoded.Header.JobBytes[0], decoded.Header.JobBytes[1], s.adventure)
if err != nil {
return SkillTreeResult{}, err
}
skills, err := s.factory.CharacterSkills(alter, equVersion)
if err != nil {
return SkillTreeResult{}, err
}
skillByUUID := make(map[string][]CharacterSkillMeta)
for _, skill := range skills {
skillByUUID[skill.UUID] = append(skillByUUID[skill.UUID], skill)
}
indexCandidates := make(map[int][]CharacterSkillMeta)
for _, item := range decoded.Skills {
if _, exists := indexCandidates[item.SkillIndex]; exists {
continue
}
indexCandidates[item.SkillIndex] = s.buildCandidates(item.SkillIndex, skillByUUID)
}
learned := make(map[string]LearnedSkill)
for _, item := range decoded.Skills {
candidates := indexCandidates[item.SkillIndex]
chosen := chooseSkillCandidate(candidates, item.SkillType)
if chosen == nil {
continue
}
current, exists := learned[chosen.ID]
if !exists || current.Lv < item.Level {
learned[chosen.ID] = LearnedSkill{Lv: item.Level, Up: 0, VP: 0, Name: chosen.Name}
}
}
for _, evo := range decoded.Evolutions {
for _, skill := range indexCandidates[evo.SkillIndex] {
if !skill.HasVP {
continue
}
if learnedSkill, ok := learned[skill.ID]; ok {
learnedSkill.VP = evo.Type
learned[skill.ID] = learnedSkill
}
}
}
for _, up := range decoded.Enhancements {
for _, skill := range indexCandidates[up.SkillIndex] {
if !skill.HasUP {
continue
}
if learnedSkill, ok := learned[skill.ID]; ok {
learnedSkill.Up = up.Type
learned[skill.ID] = learnedSkill
}
}
}
for _, skill := range skills {
if _, exists := learned[skill.ID]; exists {
continue
}
level := 0
if skill.LearnLv == 50 || skill.LearnLv == 85 || skill.Name == "基础精通" {
level = skill.MaxLearnLv
}
learned[skill.ID] = LearnedSkill{Lv: level, Up: 0, VP: 0, Name: skill.Name}
}
return SkillTreeResult{
Name: decoded.Header.Name,
Level: decoded.Header.Level,
Job: map[string]string{"class": alter},
Skills: learned,
}, nil
}
func (s *SkillTreeService) buildCandidates(index int, skillByUUID map[string][]CharacterSkillMeta) []CharacterSkillMeta {
uuids := s.indexToUID[index]
seen := make(map[string]struct{})
candidates := make([]CharacterSkillMeta, 0)
for _, uuid := range uuids {
for _, skill := range skillByUUID[uuid] {
if _, exists := seen[skill.ID]; exists {
continue
}
seen[skill.ID] = struct{}{}
candidates = append(candidates, skill)
}
}
if len(candidates) > 1 {
filtered := make([]CharacterSkillMeta, 0)
for _, skill := range candidates {
if skill.LearnLv == 50 || skill.LearnLv == 85 {
continue
}
filtered = append(filtered, skill)
}
if len(filtered) > 0 {
return filtered
}
}
return candidates
}
func chooseSkillCandidate(candidates []CharacterSkillMeta, skillType int) *CharacterSkillMeta {
if len(candidates) == 0 {
return nil
}
if len(candidates) == 1 {
return &candidates[0]
}
wantedType := "passive"
if skillType == 1 {
wantedType = "active"
}
for i := range candidates {
if candidates[i].Type == wantedType {
return &candidates[i]
}
}
return &candidates[0]
}
func loadSkillIndexMap(path string) (map[int][]string, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
uuidToIndexes := map[string][]int{}
if err := json.Unmarshal(content, &uuidToIndexes); err != nil {
return nil, err
}
indexToUUID := make(map[int][]string)
for uuid, indexes := range uuidToIndexes {
for _, idx := range indexes {
indexToUUID[idx] = append(indexToUUID[idx], uuid)
}
}
return indexToUUID, nil
}
func alterFromJobBytes(roleByte int, jobByte int, adventure *AdventureService) (string, error) {
parent, ok := roleMap[roleByte]
if !ok {
return "", fmt.Errorf("unsupported role byte: %d", roleByte)
}
jobID := jobByte - 1
if roleByte == 9 {
jobID = 1
}
if roleByte == 10 {
jobID = 0
}
token := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%02d%02d00", parent, jobID)))
return adventure.AlterByToken(token)
}
func decodeSkillCode(encoded string) (decodedSkillCode, error) {
result := decodedSkillCode{}
compressed, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return result, fmt.Errorf("decode base64: %w", err)
}
r, err := zlib.NewReader(bytes.NewReader(compressed))
if err != nil {
return result, fmt.Errorf("create zlib reader: %w", err)
}
defer r.Close()
raw, err := io.ReadAll(r)
if err != nil {
return result, fmt.Errorf("read zlib stream: %w", err)
}
if len(raw) < 14 {
return result, fmt.Errorf("invalid skill code payload")
}
nameLen := int(raw[13])
if 14+nameLen > len(raw) {
return result, fmt.Errorf("invalid name length in skill code")
}
result.Header = skillCodeHeader{
Name: decodeUTF16LE(raw[14 : 14+nameLen]),
Level: int(raw[5]),
JobBytes: [2]int{int(raw[2]), int(raw[3])},
}
data := raw[14+nameLen:]
if len(data)%2 == 1 {
data = append(data, 0)
}
uint16s := make([]uint16, 0, len(data)/2)
for i := 0; i < len(data); i += 2 {
uint16s = append(uint16s, binary.LittleEndian.Uint16(data[i:i+2]))
}
if len(uint16s) == 0 {
return result, nil
}
high := make([]byte, len(uint16s))
low := make([]byte, len(uint16s))
for i, value := range uint16s {
high[i] = byte(value >> 8)
low[i] = byte(value & 0xFF)
}
pairCount := int(high[0])
for i := 0; i < pairCount; i++ {
base := 1 + i*2
if base+1 >= len(high) {
break
}
result.Skills = append(result.Skills, skillLearnItem{
SkillIndex: int(high[base]),
Level: int(high[base+1]),
SkillType: int(low[base+1]),
})
}
sectionStart := (1 + pairCount*2) * 2
if sectionStart >= len(data) {
return result, nil
}
sec2 := data[sectionStart:]
if len(sec2) < 2 {
return result, nil
}
offset := 1
evoCount := int(sec2[offset])
offset++
for i := 0; i < evoCount; i++ {
if offset+3 > len(sec2) {
break
}
result.Evolutions = append(result.Evolutions, skillChoice{
SkillIndex: int(sec2[offset]),
Type: int(sec2[offset+2]) + 1,
})
offset += 3
}
if offset >= len(sec2) {
return result, nil
}
enhCount := int(sec2[offset])
offset++
for i := 0; i < enhCount; i++ {
if offset+3 > len(sec2) {
break
}
result.Enhancements = append(result.Enhancements, skillChoice{
SkillIndex: int(sec2[offset]),
Type: int(sec2[offset+2]) + 1,
})
offset += 3
}
return result, nil
}
func decodeUTF16LE(value []byte) string {
if len(value)%2 == 1 {
value = append(value, 0)
}
words := make([]uint16, 0, len(value)/2)
for i := 0; i < len(value); i += 2 {
words = append(words, binary.LittleEndian.Uint16(value[i:i+2]))
}
return strings.TrimSpace(string(utf16.Decode(words)))
}
+62
View File
@@ -0,0 +1,62 @@
package service
import (
"encoding/base64"
"encoding/json"
"fmt"
"time"
)
const tokenTTL = 6 * time.Hour
type TokenService struct {
adventure *AdventureService
}
type AlterTokenPayload struct {
Alter string `json:"alter"`
EquVersion string `json:"equVersion"`
Time int64 `json:"time"`
}
func NewTokenService(adventure *AdventureService) *TokenService {
return &TokenService{adventure: adventure}
}
func (s *TokenService) Create(alterToken string, equVersion string) (string, error) {
if equVersion == "" {
equVersion = "0"
}
alter, err := s.adventure.AlterByToken(alterToken)
if err != nil {
return "", err
}
payload := AlterTokenPayload{
Alter: alter,
EquVersion: equVersion,
Time: time.Now().UnixMilli(),
}
content, err := json.Marshal(payload)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(content), nil
}
func (s *TokenService) Parse(token string) (AlterTokenPayload, error) {
var payload AlterTokenPayload
content, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return payload, fmt.Errorf("decode token: %w", err)
}
if err := json.Unmarshal(content, &payload); err != nil {
return payload, fmt.Errorf("unmarshal token: %w", err)
}
if payload.Time <= 0 || time.Since(time.UnixMilli(payload.Time)) > tokenTTL {
return AlterTokenPayload{}, fmt.Errorf("token expired")
}
return payload, nil
}
+77
View File
@@ -0,0 +1,77 @@
import json
import os
import sys
def _fail(message: str) -> None:
print(json.dumps({'error': message}, ensure_ascii=False))
sys.exit(1)
def _parse_args() -> tuple[str, str, str, str]:
if len(sys.argv) < 5:
_fail('usage: python_bridge.py <project_root> <command> <alter> <equ_version>')
project_root = sys.argv[1]
command = sys.argv[2]
alter = sys.argv[3]
equ_version = sys.argv[4]
return project_root, command, alter, equ_version
def _ensure_path(project_root: str) -> None:
if project_root not in sys.path:
sys.path.insert(0, project_root)
sys.argv[0] = os.path.join(project_root, 'main.py')
os.chdir(project_root)
def _load_character(alter: str, equ_version: str):
from core.basic.character import createCharacter
character = createCharacter(alter, equ_version or '0')
if character is None:
raise RuntimeError(f'failed to create character for {alter}')
return character
def _character_info(alter: str, equ_version: str):
character = _load_character(alter, equ_version)
return character.getInfo()
def _character_skills(alter: str, equ_version: str):
info = _character_info(alter, equ_version)
skills = info.get('skills', [])
return [
{
'id': str(skill.get('id', '')),
'name': skill.get('name', ''),
'type': skill.get('type', ''),
'learnLv': int(skill.get('learnLv', 0)),
'maxLearnLv': int(skill.get('maxLearnLv', 0)),
'hasVP': bool(skill.get('hasVP', False)),
'hasUP': bool(skill.get('hasUP', False)),
'uuid': skill.get('uuid', ''),
}
for skill in skills
]
def main() -> None:
project_root, command, alter, equ_version = _parse_args()
_ensure_path(project_root)
try:
if command == 'character-info':
data = _character_info(alter, equ_version)
elif command == 'character-skills':
data = _character_skills(alter, equ_version)
else:
_fail(f'unknown command: {command}')
print(json.dumps(data, ensure_ascii=False))
except Exception as ex:
_fail(str(ex))
if __name__ == '__main__':
main()
+25 -3
View File
@@ -2,8 +2,30 @@ from core.basic.character import createCharacter
char = createCharacter('GF.gunner_female.spitfire_female')
char.calc_init({'oaths': {'0': {'id': '2010201', 'adaptation': 3}, '11': {'id': '2050200', 'adaptation': 0}}, 'equips': {}})
setInfo = {
'oaths': {'0': {'id': '2030201', 'adaptation': 3}, '1': {'id': '2030201', 'adaptation': 3}, '2': {'id': '2030201', 'adaptation': 3}, '3': {'id': '2030201', 'adaptation': 3}, '4': {'id': '2030201', 'adaptation': 3}, '11': {'id': '2030200', 'adaptation': 0}},
'oathSkill': 1,
'equips': {
'武器': {'id': '88', 'reinforce': 14, 'reinforceType': 1, 'enchant': 512, 'fusion': 0, 'refine': 8, 'adaptation': 3, 'precision': 100},
'称号': {'id': '3017', 'reinforce': 0, 'reinforceType': 0, 'enchant': 1201, 'fusion': 0, 'refine': 0, 'adaptation': 3, 'precision': 100},
'上衣': {'id': '999', 'reinforce': 13, 'reinforceType': 1, 'enchant': 105, 'fusion': '26', 'refine': 0, 'adaptation': 3, 'precision': 100},
'头肩': {'id': '1001', 'reinforce': 14, 'reinforceType': 1, 'enchant': 5, 'fusion': '28', 'refine': 0, 'adaptation': 3, 'precision': 100},
'下装': {'id': '1000', 'reinforce': 14, 'reinforceType': 1, 'enchant': 202, 'fusion': '27', 'refine': 0, 'adaptation': 3, 'precision': 100},
'鞋': {'id': '1003', 'reinforce': 14, 'reinforceType': 1, 'enchant': 309, 'fusion': '30', 'refine': 0, 'adaptation': 3, 'precision': 100},
'腰带': {'id': '1002', 'reinforce': 14, 'reinforceType': 1, 'enchant': 309, 'fusion': '29', 'refine': 0, 'adaptation': 3, 'precision': 100},
'项链': {'id': '477', 'reinforce': 13, 'reinforceType': 1, 'enchant': 609, 'fusion': '180', 'refine': 0, 'adaptation': 3, 'precision': 100},
'手镯': {'id': '483', 'reinforce': 14, 'reinforceType': 1, 'enchant': 609, 'fusion': '302', 'refine': 0, 'adaptation': 3, 'precision': 100},
'戒指': {'id': '489', 'reinforce': 14, 'reinforceType': 1, 'enchant': 609, 'fusion': '304', 'refine': 0, 'adaptation': 3, 'precision': 100},
'辅助装备': {'id': '492', 'reinforce': 14, 'reinforceType': 1, 'enchant': 905, 'fusion': '182', 'refine': 3, 'adaptation': 3, 'precision': 100},
'魔法石': {'id': '495', 'reinforce': 14, 'reinforceType': 1, 'enchant': 1003, 'fusion': '184', 'refine': 3, 'adaptation': 3, 'precision': 100},
'耳环': {'id': '498', 'reinforce': 14, 'reinforceType': 1, 'enchant': 1104, 'fusion': '186', 'refine': 0, 'adaptation': 3, 'precision': 100},
'宠物': {'id': '4022', 'reinforce': 0, 'reinforceType': 0, 'enchant': 1304, 'fusion': 0, 'refine': 0, 'adaptation': 3, 'precision': 100},
},
'emblems': {'彩色': {'lv': 1, 'items': ['4401', '4401', '4401', '4401', '4401', '4401']}, '红色': {'lv': 2, 'items': ['4101', '4101', '4101', '4101']}, '蓝色': {'lv': 2, 'items': []}, '绿色': {'lv': 3, 'items': []}, '黄色': {'lv': 4, 'items': []}},
}
for oath in char.charOathInfo:
print(oath.id, oath.adaptation, oath.oathInfo.position, oath.oathInfo.max_adaptation, oath.oathInfo.Point)
char.calc(setInfo)
# for oath in char.charOathInfo:
# print(oath.id, oath.adaptation, oath.oathInfo.position, oath.oathInfo.max_adaptation, oath.oathInfo.Point)
+1
View File
@@ -21,6 +21,7 @@ export interface IResultSkill {
}
export interface IResultUserInfo {
id: number
name: string
value: string | number
}
+5 -1
View File
@@ -26,6 +26,10 @@ export default defineRequest((request) => {
async getSkillFromCode(code: string) {
const res = await request.post(`/skillTree/`, { code })
return res.data
}
},
async getOathInfo(suitId: string, skillId: string, rarity: string) {
const res = await request.get(`/info/oath/${suitId}/${skillId}`, { params: { rarity } })
return res.data
},
}
})
+19 -1
View File
@@ -31,6 +31,7 @@ export interface ICharacterInfo {
stones: IEquipment[]
enchants: IEnchantingInfo[]
emblems: IEnchantingInfo[]
oaths: IEquipment[]
weapons: string[]
subweapons: string[]
skills: ISkill[]
@@ -39,6 +40,19 @@ export interface ICharacterInfo {
jades: IJade[]
sundry: Record<string, ISundry>
options: { id: number; name: string; options: string[] }[]
oaths_skills: Record<
string,
{
suitInfo: {
name: string
rarity: string
SkillAttack: number
Buffer: number
Attack: number
}[]
skills: { skillId: string; name: string; icon: string }[]
}
>
}
export interface ISundry {
@@ -71,6 +85,10 @@ export interface IEquipment {
Buffer: number[]
detail: string
bufferDetail: string
position: string[]
wearUrl: string[]
oathPos: string
displayUrl?: string
}
export interface ISkill {
@@ -91,7 +109,7 @@ export interface ISkill {
desc: string
}[]
uuid: string
upType:'damage' |'heal'
upType: 'damage' | 'heal'
}
export interface ISuit {
@@ -11,6 +11,7 @@ import {
renderList,
watch,
} from 'vue'
import { getElementZoom } from '@/utils/position'
type HandleChangeFunction = (value: string | number) => string[] | Promise<string[]>
@@ -55,10 +56,12 @@ export default defineComponent({
function onResize() {
if (triggerRef.value) {
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
const zoom = getElementZoom('body')
dropdownPosition.value = {
w: width,
x: left,
y: top + height + 2,
w: width / zoom,
x: left / zoom,
y: (top + height + 2) / zoom,
}
}
}
@@ -4,6 +4,7 @@ import type { CSSProperties, PropType } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, watch } from 'vue'
import { onClickOutside, useVModel } from '@vueuse/core'
import { getElementZoom } from '@/utils/position'
interface ICascaderItem {
label: string
id?: string | number
@@ -74,18 +75,19 @@ export default defineComponent({
function onResize() {
if (triggerRef.value) {
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
const zoom = getElementZoom('body')
if (window.innerHeight - top > 160) {
dropdownPosition.value = {
w: width,
x: left,
y: top + height + 2,
w: width / zoom,
x: left / zoom,
y: (top + height + 2) / zoom,
z: 0,
}
} else {
dropdownPosition.value = {
w: width,
x: left,
z: window.innerHeight - top + 2,
w: width / zoom,
x: left / zoom,
z: (window.innerHeight - top + 2) / zoom,
y: 0,
}
}
@@ -13,6 +13,7 @@ import {
} from 'vue'
import { onClickOutside } from '@vueuse/core'
import { getElementZoom } from '@/utils/position'
export default defineComponent({
name: 'IIconselect',
@@ -72,20 +73,24 @@ export default defineComponent({
const dropdownPosition = ref({ x: 0, y: 0, w: 0 })
// 下拉框位置
const dropdownStyle = computed<CSSProperties>(() => {
const zoom = getElementZoom(appendToRef.value)
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${columnNum.value * 32 + 11}px`,
width: `${(columnNum.value * 32 + 11) / zoom}px`,
}
})
function onResize() {
if (triggerRef.value) {
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
const zoom = getElementZoom(appendToRef.value)
dropdownPosition.value = {
w: width,
x: left - 2,
y: (top + height + 2),
w: width / zoom,
x: (left - 2) / zoom,
y: (top + height + 2) / zoom,
}
}
}
@@ -15,6 +15,7 @@ import {
import { onClickOutside, useVModel } from '@vueuse/core'
import { classPropType, labelPropType } from '@/components/hooks/types'
import { getElementZoom } from '@/utils/position'
export default defineComponent({
name: 'ISelect',
@@ -98,18 +99,19 @@ export default defineComponent({
function onResize() {
if (triggerRef.value) {
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
const zoom = getElementZoom(appendToRef.value)
if (window.innerHeight - top > 160) {
dropdownPosition.value = {
w: width,
x: left,
y: (top + height + 2),
w: width / zoom,
x: left / zoom,
y: (top + height + 2) / zoom,
z: 0,
}
} else {
dropdownPosition.value = {
w: width,
x: left,
z: (window.innerHeight - top + 2),
w: width / zoom,
x: left / zoom,
z: (window.innerHeight - top + 2) / zoom,
y: 0,
}
}
@@ -13,9 +13,8 @@
</template>
<script lang="ts" setup name="EquList">
import { getImageURL } from '@/utils/images'
import { useVModel } from '@vueuse/core'
import { useInfoStore, useConfigStore } from '@/stores'
import { useInfoStore } from '@/stores'
const props = defineProps({
part: {
@@ -29,7 +28,7 @@ const infoStore = useInfoStore()
const infoStyle = (part: string) => {
let x = 15
let y = 5
let index = infoStore.avatarParts.findIndex((p) => p == part)
const index = infoStore.avatarParts.findIndex((p) => p == part)
// if (index == 13) {
// index -= 7
// } else if (index >= 5 && index <= 12) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,135 @@
<template>
<div class="pos-relative w-248px h-full" :class="props.withBg ? 'emblem-bg' : ''">
<template v-for="part in parts" :key="part">
<div
class="pos-absolute text-white/50 flex items-center justify-center rounded-50%"
:style="infoStyle(part)"
@click="choosePart(part)"
>
<img
class="w-20px h-20px"
style="object-fit: none; object-position: hue; mix-blend-mode: screen"
:src="rarityIcon(part)"
v-if="part != '白金'"
/>
<img class="pos-absolute" :style="lvStyle" :src="lvIcon(part)" v-if="part != '白金'" />
</div>
</template>
</div>
</template>
<script lang="ts" setup name="EmblemList">
import { useInfoStore, type IConfigEmblem } from '@/stores'
import { getImageURL } from '@/utils/images'
const props = defineProps({
withBg: {
type: Boolean,
default: true,
},
detail: {
type: Object as PropType<Record<string, IConfigEmblem>>,
},
})
const infoStore = useInfoStore()
const partModelValue = defineModel('part', { default: '彩色' })
const partIcon = computed(() => {
return (part: string) => {
return new URL(`../Images/part/${part}.png`, import.meta.url).href
}
})
const rarityIcon = computed(() => {
return (part: string) => {
const rarity = Math.floor((props.detail?.[part]?.lv || 0) / 5)
console.log(rarity)
return new URL(`../Images/rarity/${rarity}.png`, import.meta.url).href
}
})
const lvIcon = computed(() => {
return (part: string) => {
const lv = (props.detail?.[part]?.lv || 0) % 5
return getImageURL(`/equipment/adaptation/${lv + 1}.png`)
}
})
const position: Record<string, { x: number; y: number; width?: number; height?: number }> = {
彩色: {
x: 99,
y: 15,
},
红色: {
x: 18,
y: 39,
},
蓝色: {
x: 180,
y: 39,
},
黄色: {
x: 65,
y: 83,
},
绿色: {
x: 133,
y: 83,
},
白金: {
x: 33,
y: 117,
width: 24,
height: 24,
},
增益: {
x: 192,
y: 117,
width: 24,
height: 24,
},
}
const lvStyle = computed(() => {
return {
left: '50%',
top: '100%',
transform: 'translate(-50%,-100%)',
zIndex: 7,
}
})
const infoStyle = computed(() => {
return (part: string) => {
console.log(`url(${rarityIcon.value(part)}),url(${partIcon.value(part)})`)
return {
left: `${position[part as keyof typeof position].x}px`,
top: `${position[part as keyof typeof position].y}px`,
width: `${position[part as keyof typeof position]?.width ?? 50}px`,
height: `${position[part as keyof typeof position]?.height ?? 50}px`,
backgroundImage: `url(${partIcon.value(part)})`,
// backgroundImage: `url(${rarityIcon.value(part)}),url(${partIcon.value(part)})`,
// backgroundImage: `url(${partIcon.value(part)}),url(${rarityIcon.value(part)})`,
// backgroundBlendMode: 'screen',
zIndex: 6,
// fontWeight: 900,
}
}
})
const parts = computed(() => {
const parts = infoStore.emblemParts
return parts
})
const choosePart = (part: string) => {
partModelValue.value = part
}
</script>
<style lang="scss" scoped>
.emblem-bg {
background-image: url(../Images/head.png);
}
</style>
@@ -3,7 +3,9 @@
<div class="relative" :class="{ inactive: props.inactive }">
<div
class="w-28px h-28px"
:style="{ backgroundImage: `url(${getImageURL(props.equipment?.imageUrl)})` }"
:style="{
backgroundImage: `url(${getImageURL((useDisplayUrl ? props.equipment?.displayUrl : props.equipment?.imageUrl) || '')})`,
}"
>
<img :src="getLocalImageURL(rarity)" />
</div>
@@ -21,6 +23,7 @@ import { getImageURL } from '@/utils/images'
const props = defineProps<{
equipment?: IEquipment
inactive?: boolean
useDisplayUrl?: boolean
}>()
const rarity = computed(() => {
@@ -38,16 +38,12 @@
</div>
<!-- 装备属性 -->
<div class="divide"></div>
<div
class="flex flex-col items-start my-1px whitespace-pre-wrap leading-15px"
>
<div class="flex flex-col items-start my-1px whitespace-pre-wrap leading-15px">
<div class="text-#DED29A py-4px whitespace-pre-wrap" v-html="detail"></div>
</div>
<template v-if="bufferDetail">
<div class="divide"></div>
<div
class="flex flex-col items-start my-1px whitespace-pre-wrap leading-15px"
>
<div class="flex flex-col items-start my-1px whitespace-pre-wrap leading-15px">
<div class="text-#A48B2E mb-10px pt-4px">辅助职业专属属性</div>
<div class="text-#DED29A pb-10px whitespace-pre-wrap" v-html="bufferDetail"></div>
</div>
@@ -95,16 +91,18 @@ const mainAttr = computed(() => {
// a.match(/\[(?!.*?技能).*?]/g)
const detail = computed(() =>
props.equipment?.detail.replace(
/\[(?![^\]]*技能)[^\[\]]+\]/g, match => `<span style="color:#3d9147">${match}</span>`
)
props.equipment?.detail?.replace(
/\[(?![^\]]*技能)[^\[\]]+\]/g,
(match) => `<span style="color:#3d9147">${match}</span>`,
),
)
const bufferDetail = computed(() => {
const detail = props.equipment?.bufferDetail
if (!detail || detail == '-') return undefined
return props.equipment?.bufferDetail.replace(
/\[(?![^\]]*技能)[^\[\]]+\]/g, match => `<span style="color:#3d9147">${match}</span>`
/\[(?![^\]]*技能)[^\[\]]+\]/g,
(match) => `<span style="color:#3d9147">${match}</span>`,
)
})
</script>
@@ -16,9 +16,9 @@
{{ partName(part) }}
</template>
</div>
<div v-if="detail?.[part]?.fusion" class="pos-absolute" :style="fusionStyle(part)">
<EquipmentIcon :equipment="stones(detail[part].fusion)" />
</div>
<!-- <div v-if="detail?.[part]?.fusion" class="pos-absolute" :style="fusionStyle(part)">
<EquipmentIcon :equipment="oaths(detail[part].fusion)" />
</div> -->
</template>
<div
v-if="props.withSubWeapon"
@@ -69,7 +69,7 @@ const props = defineProps({
avatar: {
type: String,
default: '',
}
},
})
const partModelValue = useVModel(props, 'part')
const infoStore = useInfoStore()
@@ -90,13 +90,13 @@ const equ = (id: string) => {
return infoStore.equips.find((e) => e.id == id)
}
const stones = (id: string) => {
return infoStore.stones.find((e) => e.id == id)
}
// const oaths = (id: string) => {
// return infoStore.oaths.find((e) => e.id == id)
// }
const infoStyle = (part: string) => {
let x = 10
let y = 5
let y = 10
let index = infoStore.parts.findIndex((p) => p == part)
if (index == 13) {
index -= 7
@@ -117,30 +117,30 @@ const infoStyle = (part: string) => {
return style
}
const fusionStyle = (part: string) => {
let x = 0
let y = 15
let index = infoStore.parts.findIndex((p) => p == part)
// const fusionStyle = (part: string) => {
// let x = 0
// let y = 15
// let index = infoStore.parts.findIndex((p) => p == part)
if (index >= 5 && index <= 12) {
x += 179
index -= 5
}
// if (index >= 5 && index <= 12) {
// x += 179
// index -= 5
// }
if (index == 13) {
index = 5
}
// if (index == 13) {
// index = 5
// }
x += (index % 2) * 39
y += Math.floor(index / 2) * 34
// x += (index % 2) * 39
// y += Math.floor(index / 2) * 34
return {
left: `${x}px`,
top: `${y}px`,
zIndex: 7,
transform: 'scale(0.6)',
}
}
// return {
// left: `${x}px`,
// top: `${y}px`,
// zIndex: 7,
// transform: 'scale(0.6)',
// }
// }
const choosePart = (part: string) => {
partModelValue.value = part
}
@@ -0,0 +1,182 @@
<template>
<div
class="pos-relative w-264px h-full"
:style="{ backgroundImage: withBg ? `url(${getImageURL('/common/oath/head.png')})` : 'none' }"
>
<template v-for="part in parts" :key="part">
<div
class="pos-absolute text-white/50 w-26px h-26px border-1px border-solid border-#7a7a7a/50 flex items-center justify-center"
:style="infoStyle(part)"
@click="choosePart(part)"
>
<template v-if="detail?.[part]?.id">
<EquipmentIcon :equipment="equ(detail[part].id, part)" useDisplayUrl />
</template>
<template v-else-if="!withBg">
{{ part != '11' ? '星蕴' : '誓约' }}
</template>
</div>
<!-- <div v-if="detail?.[part]?.fusion" class="pos-absolute" :style="fusionStyle(part)">
<EquipmentIcon :equipment="oaths(detail[part].fusion)" />
</div> -->
</template>
<!-- <img
v-if="props.avatar"
:src="props.avatar"
alt="Avatar"
class="pos-absolute w-70% left-1/2 translate-x--1/2 -bottom-10px"
/> -->
</div>
</template>
<script lang="ts" setup name="EquList">
import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
import { useInfoStore, type IConfigOath } from '@/stores'
const props = defineProps({
withBg: {
type: Boolean,
default: true,
},
detail: {
type: Object as PropType<Record<string, IConfigOath>>,
},
})
const partModelValue = defineModel('part', { default: '0' })
const infoStore = useInfoStore()
const parts = computed(() => {
const parts = infoStore.oathParts
return parts
})
const equ = (id: string, part: string) => {
return infoStore.oaths.find((e) => e.id == id && e.oathPos == part)
}
const position = [
{
x: 30,
y: 12,
},
{
x: 30,
y: 46,
},
{
x: 30,
y: 80.5,
},
{
x: 30,
y: 114,
},
{
x: 206,
y: 12,
},
{
x: 206,
y: 46,
},
{
x: 206,
y: 80.5,
},
{
x: 206,
y: 114,
},
{
x: 74,
y: 131.5,
},
{
x: 118,
y: 145.5,
},
{
x: 162,
y: 131.5,
},
{
x: 118,
y: 111,
},
]
const positionSimple = [
{
x: 30,
y: 12,
},
{
x: 30,
y: 46,
},
{
x: 30,
y: 80.5,
},
{
x: 30,
y: 114,
},
{
x: 206,
y: 12,
},
{
x: 206,
y: 46,
},
{
x: 206,
y: 80.5,
},
{
x: 206,
y: 114,
},
{
x: 74,
y: 114,
},
{
x: 118,
y: 114,
},
{
x: 162,
y: 114,
},
{
x: 118,
y: 80.5,
},
]
const infoStyle = (part: string) => {
if (props.withBg)
return {
left: `${position[parseInt(part)].x}px`,
top: `${position[parseInt(part)].y}px`,
zIndex: 6,
// fontWeight: 900,
}
else
return {
left: `${positionSimple[parseInt(part)].x}px`,
top: `${positionSimple[parseInt(part)].y}px`,
zIndex: 6,
// fontWeight: 900,
}
}
const choosePart = (part: string) => {
partModelValue.value = part
}
</script>
<style lang="scss" scoped></style>
@@ -18,7 +18,7 @@
<Suit v-show="equTab === 0"></Suit>
<Universal v-show="equTab === 1"></Universal>
</calc-collapse>
<calc-collapse title="融合石">
<calc-collapse title="誓约">
<calc-tabs v-model="stoneTab" class="!mb-2">
<calc-tab :value="0">套装</calc-tab>
<calc-tab :value="1">通宝</calc-tab>
@@ -1,60 +0,0 @@
`<!-- 每排总计6+4+4,共计14个排列 -->
<template>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip, _) in equs" :key="_">
<EquipmentIcon
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
:equipment="equip"
@click="configStore.chooseEqu(equip, true)"
/>
</template>
</div>
</template>
<script lang="ts" setup StoneUniversal>
import type { IEquipment } from '@/api/info/type'
import { useConfigStore, useInfoStore } from '@/stores'
import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
const props = defineProps<{
rowCount?: number
}>()
const rowCount = Math.max(props.rowCount ?? 0, 14)
const infoStore = useInfoStore()
const configStore = useConfigStore()
const equs = computed(() => {
const items = infoStore.stones.filter((equip) => equip.categorize.includes('神界'))
const res: (IEquipment | undefined)[] = []
res.push(
...items.filter((a) => a.rarity === '太初'),
...Array(Math.max(0, rowCount - items.filter((a) => a.rarity === '太初').length)).fill(
undefined,
),
)
;['手镯', '项链', '戒指', '', '辅助装备', '魔法石', '耳环', ''].forEach((cat) => {
const equ: (IEquipment | undefined)[] = items.filter(
(a) => a.rarity === '史诗' && a.itemDetailType == cat,
)
res.push(...equ, ...Array(Math.max(0, 7 - equ.length)).fill(undefined))
})
// const categorize = Array.from(new Set(items.map((item) => item.categorize)))
// raritiyList.forEach((rarity) => {
// categorize.forEach((cat) => {
// const equ : (IEquipment | undefined)[] = items.filter((a) => a.rarity === rarity && a.categorize === cat)
// equ.splice(5,0,undefined)
// res.push(
// ...equ,
// ...Array(Math.max(0,rowCount - equ.length)).fill(undefined),
// )
// })
// })
return res
})
</script>
`
@@ -15,7 +15,6 @@
<script lang="ts" setup>
import type { IEquipment } from '@/api/info/type'
import { useConfigStore, useInfoStore } from '@/stores'
import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
const props = defineProps<{
@@ -15,10 +15,10 @@
<div class="w-auto text-18px flex items-center justify-center !font-normal">|</div>
<div
class="flex-1 text-15px flex items-center justify-center"
@click="chooseCategory('stone')"
:class="{ active: type === 'stone' }"
@click="chooseCategory('oath')"
:class="{ active: type === 'oath' }"
>
融合石
誓约
</div>
</div>
<div
@@ -71,51 +71,129 @@
</div>
</div>
<div class="px-5px flex flex-col">
<div class="flex flex-col overflow-hidden bg-black" v-if="curEquInfo">
<div class="item-head">基础信息</div>
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
<!-- 基础信息 -->
<div
class="flex flex-col overflow-hidden bg-black"
:class="!hideBase ? '' : 'mb-4px'"
v-if="curEquInfo"
>
<div class="item-head" @click="hideBase = !hideBase">
基础信息{{ hideBase ? ' (点击显示)' : '(点击隐藏)' }}
</div>
<div v-show="!hideBase" class="overflow-y-auto overflow-x-hidden bg-black w-240px">
<Info v-if="curEquInfo" :equipment="curEquInfo" />
</div>
</div>
<div class="flex flex-col flex-1 overflow-hidden bg-black" v-if="curSuitInfo">
<div class="item-head">套装信息</div>
<!-- 套装信息 -->
<div
class="flex flex-col overflow-hidden bg-black"
:class="!hideSuit ? 'flex-1' : 'mb-4px'"
v-if="curSuitInfo"
>
<div class="item-head" @click="hideSuit = !hideSuit">
套装信息{{ hideSuit ? ' (点击显示)' : '(点击隐藏)' }}
</div>
<template v-if="!hideSuit">
<div class="flex bg-#191918 items-center justify-center gap-2px p-5px">
<calc-button
:disabled="raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0"
icon="min-little"
@click="changeSuitLv('preRarity')"
></calc-button>
<calc-button
icon="reduce-little"
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0 &&
curSuitLv.lv <= 1
"
@click="changeSuitLv('preLv')"
></calc-button>
<div class="flex px-20px gap-x-10px" :class="rarityClass(curSuitLv.rarity)">
<span>{{ curSuitLv.rarity }}</span>
<span>{{ formatLv(curSuitLv.lv, curSuitLv.rarity) }}</span>
</div>
<calc-button
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >=
raritiyList.length - 1
"
@click="changeSuitLv('nextLv')"
icon="increase-little"
></calc-button>
<calc-button
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >=
raritiyList.length - 1
"
@click="changeSuitLv('nextRarity')"
icon="max-little"
></calc-button>
</div>
<div
class="w-80% m-auto h-1px bg-gradient-to-r from-[rgba(52,43,27,0.05)] via-[rgba(52,43,27,1)] to-[rgba(52,43,27,0.05)]"
></div>
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
<SuitInfo v-if="curSuitInfo" :suit="curSuitInfo" />
</div>
</template>
</div>
<div class="flex flex-col flex-1 overflow-hidden bg-black" v-if="curOathInfo">
<div class="item-head">誓约信息</div>
<div class="flex bg-#191918 items-center justify-center gap-2px p-5px">
<calc-button
:disabled="raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0"
:disabled="raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) <= 0"
icon="min-little"
@click="changeSuitLv('preRarity')"
@click="changeOathSkillLv('preRarity')"
></calc-button>
<calc-button
icon="reduce-little"
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0 && curSuitLv.lv <= 1
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) <= 0 &&
curOathSkillLv.lv <= 1
"
@click="changeSuitLv('preLv')"
@click="changeOathSkillLv('preLv')"
></calc-button>
<div class="flex px-20px gap-x-10px" :class="rarityClass(curSuitLv.rarity)">
<span>{{ curSuitLv.rarity }}</span>
<span>{{ formatLv(curSuitLv.lv, curSuitLv.rarity) }}</span>
<div class="flex px-20px gap-x-10px" :class="rarityClass(curOathSkillLv.rarity)">
<span>{{ curOathSkillLv.rarity }}</span>
<span>{{ formatLv(curOathSkillLv.lv, curOathSkillLv.rarity) }}</span>
</div>
<calc-button
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >= raritiyList.length - 1
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) >=
raritiyList.length - 1
"
@click="changeSuitLv('nextLv')"
@click="changeOathSkillLv('nextLv')"
icon="increase-little"
></calc-button>
<calc-button
:disabled="
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >= raritiyList.length - 1
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) >=
raritiyList.length - 1
"
@click="changeSuitLv('nextRarity')"
@click="changeOathSkillLv('nextRarity')"
icon="max-little"
></calc-button>
</div>
<!-- 技能信息 -->
<div class="flex items-center justify-center py-5px">
<template v-for="skill in curOathInfo.skills" :key="skill.name">
<div
class="flex-1 flex flex-col items-center justify-center"
:class="{ unActive: skill.skillId != oathSkillId }"
@click="oathSkillId = skill.skillId"
>
<img class="w-40px h-40px" :src="getImageURL(skill.icon)" :alt="skill.name" />
<div class="text-#DED29A">{{ skill.name }}</div>
</div>
</template>
</div>
<div
class="w-80% m-auto h-1px bg-gradient-to-r from-[rgba(52,43,27,0.05)] via-[rgba(52,43,27,1)] to-[rgba(52,43,27,0.05)]"
></div>
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
<SuitInfo v-if="curSuitInfo" :suit="curSuitInfo" />
<SuitInfo v-if="curOathSkillInfo" :suit="curOathSkillInfo" />
</div>
</div>
</div>
@@ -130,7 +208,7 @@ import type { IEquipment } from '@/api/info/type'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
import Info from '@/components/dnf/Equipment/Info/index.vue'
import SuitInfo from '@/components/dnf/Equipment/SuitInfo/index.vue'
import { formatEqu, formatStone } from './formatData'
import { formatEqu, formatOath } from './formatData'
import options from './components/options.vue'
// const props = defineProps<{
@@ -143,11 +221,19 @@ const curRarity = ref('')
const curSuit = ref(16203)
const curEquId = ref()
const curSuitLv = ref({
rarity: '太初',
lv: 1,
})
const curOathSkillLv = ref({
rarity: '史诗',
lv: 1,
})
const triggerVisible = ref(false)
const hideBase = ref(false)
const hideSuit = ref(false)
const oathSkillId = ref('1')
const raritiyList = ['稀有', '神器', '传说', '史诗', '太初']
const formatLv = (number: number, raritiy?: string) => {
@@ -191,13 +277,48 @@ const changeSuitLv = (
}
}
const changeOathSkillLv = (
type: 'preRarity' | 'nextRarity' | 'preLv' | 'nextLv',
resetLv: boolean = true,
) => {
if (type == 'preRarity') {
const index = raritiyList.findIndex((a) => a == curOathSkillLv.value.rarity)
if (index > 0) {
curOathSkillLv.value.rarity = raritiyList[index - 1]
curOathSkillLv.value.lv = resetLv ? 1 : 5
return
}
} else if (type == 'nextRarity') {
const index = raritiyList.findIndex((a) => a == curOathSkillLv.value.rarity)
if (index < raritiyList.length - 1) {
curOathSkillLv.value.rarity = raritiyList[index + 1]
curOathSkillLv.value.lv = 1
return
}
} else if (type == 'preLv') {
if (curOathSkillLv.value.lv > 1) {
curOathSkillLv.value.lv--
return
} else {
changeOathSkillLv('preRarity', false)
}
} else if (type == 'nextLv') {
if (curOathSkillLv.value.lv < 5) {
curOathSkillLv.value.lv++
return
} else {
changeOathSkillLv('nextRarity')
}
}
}
const choose_part = ref<string[]>([])
const curEquInfo = computed(() => {
if (type.value == 'equ') {
return infoStore.equips.find((a) => a.id == curEquId.value)
} else {
return infoStore.stones.find((a) => a.id == curEquId.value)
return infoStore.oaths.find((a) => a.id == curEquId.value)
}
})
@@ -213,9 +334,42 @@ const curSuitInfo = computed(() => {
)
})
const curOathInfo = computed(() => {
if (curEquInfo.value?.suit.length != 1 || curEquInfo.value?.oathPos == undefined) {
return undefined
}
return infoStore.oathSkills?.[curEquInfo.value?.suit[0]]
})
const curOathSkillLvs = asyncComputed(async () => {
if (!curOathInfo.value || !curEquInfo.value?.suit[0]) {
return undefined
}
const res = await infoStore.getOathInfo(
curEquInfo.value?.suit[0],
oathSkillId.value,
curOathSkillLv.value.rarity,
)
return res
})
const curOathSkillInfo = computed(() => {
const res = curOathSkillLvs.value
if (!res) {
return undefined
}
console.log(curEquInfo.value?.suit[0])
return {
...res,
SkillAttack: parseFloat(res.SkillAttack.toString().split(',')[curOathSkillLv.value.lv - 1]),
Buffer: parseFloat(res.Buffer.toString().split(',')[curOathSkillLv.value.lv - 1]),
point: parseFloat(res.point.toString().split(',')[curOathSkillLv.value.lv - 1]),
}
})
const chooseCategory = (cat: string) => {
type.value = cat
if (type.value == 'stone' && curSuit.value == -1) {
if (type.value == 'oath' && curSuit.value == -1) {
curSuit.value = 16203
}
}
@@ -240,14 +394,14 @@ const suitList = computed(() => {
const equs = computed(() => {
// 筛选出装备
const total = (type.value == 'equ' ? infoStore.equips : infoStore.stones).filter(
const total = (type.value == 'equ' ? infoStore.equips : infoStore.oaths).filter(
(a) =>
(!curRarity.value || a.rarity === curRarity.value) &&
(choose_part.value.length === 0 ||
choose_part.value.includes(a.itemDetailType || a.itemType)) &&
(a.suit.length == 0 ||
(curSuit.value == -1 && a.suit.length > 1 && type.value == 'equ') ||
(a.suit.length > 1 && type.value == 'stone') ||
(a.suit.length > 1 && type.value == 'oath') ||
(a.suit.includes(curSuit.value.toString()) && a.suit.length === 1)),
)
@@ -258,7 +412,10 @@ const equs = computed(() => {
suitList.value.find((a) => a.value == curSuit.value)?.name,
)
} else {
return formatStone(total, suitList.value.find((a) => a.value == curSuit.value)?.name)
return formatOath(
total as unknown as IEquipment[],
suitList.value.find((a) => a.value == curSuit.value),
)
}
})
@@ -272,7 +429,7 @@ const isActive = computed(() => {
return configStore.config.equips[item.itemDetailType].id == item.id
}
} else {
return configStore.config.equips[item.itemDetailType].fusion == item.id
return configStore.config.oaths[item.oathPos].id == item.id
}
}
})
@@ -285,8 +442,8 @@ const chooseEqu = (item?: IEquipment) => {
? configStore.config.equips[
item.itemType.includes('武器') ? item.itemType : item.itemDetailType
]
: configStore.config.equips[item.itemDetailType]
target[type.value === 'equ' ? 'id' : 'fusion'] = isActive.value(item) ? '' : item.id
: configStore.config.oaths[item.oathPos]
target['id'] = isActive.value(item) ? '' : item.id
}
</script>
@@ -323,4 +480,8 @@ const chooseEqu = (item?: IEquipment) => {
border-bottom: 1px solid #211d15;
text-align: center;
}
.unActive {
filter: grayscale(100%);
}
</style>
@@ -7,23 +7,25 @@ export const formatEqu = (total: IEquipment[], subweapons?: string[], curSuit?:
const item_weapon = total.filter((a) => a.itemType == '武器') ?? []
const weapons: (IEquipment | undefined)[] = []
if (item_weapon.length > 0) {
Array.from(new Set(item_weapon.map((item) => item.rarity))).filter(a=>!['稀有','神器'].includes(a)).forEach((rarity) => {
const weapon = item_weapon
.filter((item) => item.rarity === rarity)
.sort((a, b) => {
if (a.categorize == b.categorize) {
return a.itemDetailType.localeCompare(b.itemDetailType)
}
return b.categorize.localeCompare(a.categorize)
})
weapons.push(
...weapon.filter((a) => !['传世武器升级', '狄瑞吉传世武器'].includes(a.categorize)),
)
const remind = weapons.length % rowCount
if (remind > 0) {
weapons.push(...Array(rowCount - remind).fill(undefined))
}
})
Array.from(new Set(item_weapon.map((item) => item.rarity)))
.filter((a) => !['稀有', '神器'].includes(a))
.forEach((rarity) => {
const weapon = item_weapon
.filter((item) => item.rarity === rarity)
.sort((a, b) => {
if (a.categorize == b.categorize) {
return a.itemDetailType.localeCompare(b.itemDetailType)
}
return b.categorize.localeCompare(a.categorize)
})
weapons.push(
...weapon.filter((a) => !['传世武器升级', '狄瑞吉传世武器'].includes(a.categorize)),
)
const remind = weapons.length % rowCount
if (remind > 0) {
weapons.push(...Array(rowCount - remind).fill(undefined))
}
})
if (item_weapon.some((a) => a.categorize === '传世武器升级')) {
const length = item_weapon.filter((a) => a.categorize === '传世武器升级').length
weapons.push(...Array(length).fill(undefined))
@@ -88,9 +90,13 @@ export const formatEqu = (total: IEquipment[], subweapons?: string[], curSuit?:
(equip) => equip.categorize.includes('套装') || equip.categorize.includes('通宝'),
)
const suits: (IEquipment | undefined)[] = []
const raritiyList = Array.from(new Set(item_suits.map((item) => item.rarity))).filter(a=>!['稀有','神器'].includes(a))
const raritiyList = Array.from(new Set(item_suits.map((item) => item.rarity))).filter(
(a) => !['稀有', '神器'].includes(a),
)
raritiyList.forEach((rarity) => {
const armor = item_suits.filter((item) => item.rarity === rarity && item.itemType === '防具' && !item.name.includes("侵蚀"))
const armor = item_suits.filter(
(item) => item.rarity === rarity && item.itemType === '防具' && !item.name.includes('侵蚀'),
)
if (armor.length > 0) {
suits.push(...armor, undefined)
}
@@ -119,7 +125,9 @@ export const formatEqu = (total: IEquipment[], subweapons?: string[], curSuit?:
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
}
const armorUpgrade = item_suits.filter((item) => item.rarity === rarity && item.itemType === '防具' && item.name.includes("侵蚀"))
const armorUpgrade = item_suits.filter(
(item) => item.rarity === rarity && item.itemType === '防具' && item.name.includes('侵蚀'),
)
if (armorUpgrade.length > 0) {
suits.push(...armorUpgrade)
}
@@ -283,3 +291,32 @@ export const formatStone = (total: IEquipment[], curSuit?: string) => {
// },
]
}
export const formatOath = (
total: IEquipment[],
curSuit?: { name: string; value: number; imageUrl: string },
) => {
// oath
const oath = total.filter((equip) => equip.itemDetailType == '誓约')
oath.push(...Array(rowCount - (oath.length % rowCount)).fill(undefined))
// primer
const primer = []
const primerItem = total.filter((equip) => equip.itemDetailType == '星蕴石')
for (let i = 0; i < 11; i++) {
const temp = primerItem.filter((a) => a.oathPos == i.toString())
primer.push(
...temp,
...Array(temp.length == 0 ? rowCount : rowCount - (temp.length % rowCount)).fill(undefined),
)
}
return [
{
name: `${curSuit?.name} 誓约`,
equs: oath,
},
{
name: `${curSuit?.name} 星蕴石`,
equs: primer,
},
]
}
@@ -5,14 +5,12 @@ import { useInfoStore, useConfigStore } from '@/stores'
import ClothList from '@/components/dnf/Avatar/List/index.vue'
export default defineComponent({
name: 'Clothes',
setup(props) {
setup() {
const configStore = useConfigStore()
const part = ref('武器装扮')
// const globalStore = useGlobalStore()
/** 是否有徽章 */
const has_socket = computed(() => ['武器装扮', '光环', '皮肤'].includes(part.value as string))
const basicInfoStore = useInfoStore()
const has_enchant = computed(() => {
return !['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋'].includes(
@@ -26,9 +24,9 @@ export default defineComponent({
.sort((a, b) => (b.fame ?? 0) - (a.fame ?? 0))
})
const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
})
// const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
// return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
// })
const currentInfo = function <T>(name: string, defaultValue?: T) {
return computed<string | number>({
@@ -36,7 +34,7 @@ export default defineComponent({
let key: 'avatar' | 'equips' = 'avatar'
if (part.value == '宠物') key = 'equips'
return (
(configStore.config[key]?.[part.value] as Record<string, any>)?.[name] ??
(configStore.config[key]?.[part.value] as Record<string, string | number>)?.[name] ??
defaultValue ??
0
)
@@ -48,10 +46,13 @@ export default defineComponent({
let key: 'avatar' | 'equips' = 'avatar'
if (part.value == '宠物') key = 'equips'
if (!configStore.config[key][part.value]) {
configStore.config[key][part.value] = key === 'avatar' ? { ...configStore.defaultAvatarConfig } : { ...configStore.defaultEqusConfig }
configStore.config[key][part.value] =
key === 'avatar'
? { ...configStore.defaultAvatarConfig }
: { ...configStore.defaultEqusConfig }
}
if (configStore.config[key][part.value]) {
;(configStore.config[key][part.value] as Record<string, any>)[name] = val
;(configStore.config[key][part.value] as Record<string, string | number>)[name] = val
}
},
})
@@ -60,11 +61,11 @@ export default defineComponent({
// 附魔
const enchant = currentInfo<string | number>('enchant')
// 镶嵌栏1
const emblem_0 = currentInfo<string | number>('emblem_0', 0)
// // 镶嵌栏1
// const emblem_0 = currentInfo<string | number>('emblem_0', 0)
// 镶嵌栏2
const emblem_1 = currentInfo<string | number>('emblem_1')
// // 镶嵌栏2
// const emblem_1 = currentInfo<string | number>('emblem_1')
// 品级
const id = currentInfo<string | number>('id')
@@ -76,9 +77,9 @@ export default defineComponent({
* 同步徽章1到徽章2
* @param val
*/
function changeSocket(val: number) {
emblem_1.value = val
}
// function changeSocket(val: number) {
// emblem_1.value = val
// }
return () => {
return (
@@ -127,33 +128,6 @@ export default defineComponent({
</div>
</>
)}
{has_socket.value ? (
<div class="equ-profile-item">
<div class="row-name">徽章</div>
<calc-select
onChange={changeSocket}
v-model={emblem_0.value}
class="flex-1 !h-20px"
>
<calc-option value={0}>无</calc-option>
{renderList(emblem_list.value ?? [], (item) => (
<calc-option
value={item.id}
>{`${item.rarity}${item.categorize}徽章[${item.detail}]`}</calc-option>
))}
</calc-select>
<calc-select v-model={emblem_1.value} class="flex-1 !h-20px">
<calc-option value={0}>无</calc-option>
{renderList(emblem_list.value ?? [], (item) => (
<calc-option
value={item.id}
>{`${item.rarity}${item.categorize}徽章[${item.detail}]`}</calc-option>
))}
</calc-select>
</div>
) : (
<div></div>
)}
</div>
</>
)
@@ -0,0 +1,156 @@
<template>
<div class="h-179px flex justify-center items-center">
<EmblemList :withBg="true" v-model:part="part" :detail="configStore.config.emblems" />
</div>
<div class="flex flex-wrap equ-profile">
<div class="equ-profile-item">
<div class="mr-10px row-name">当前槽位</div>
<span>{{ part }}</span>
</div>
<div class="equ-profile-item" v-if="part != '白金'">
<div class="row-name">等级</div>
<calc-select v-model="lv" class="flex-1 !h-20px">
<template v-for="(i, index) in lvs" :key="index">
<calc-option :value="index">{{ i }}</calc-option>
</template>
</calc-select>
</div>
<div class="flex w-full">
<div class="flex-basis-50px text-center mr-10px">徽章</div>
<div class="flex-1 grid gap-4px" :style="emblemGridStyle">
<div
v-for="(emblem, index) in emblemMaxCount"
:style="emblemItemStyle(index)"
class="flex"
:key="index"
>
<calc-select v-model="emblems[index]" class="min-w-0 flex-1 !h-20px">
<template v-for="(i, index) in lvs" :key="index">
<calc-option :value="i">{{ i }}</calc-option>
</template>
</calc-select>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup name="Emblems">
import EmblemList from '@/components/dnf/Emblem/List/index.vue'
import { useConfigStore } from '@/stores'
const part = ref('彩色')
const configStore = useConfigStore()
const lvs = [
'稀有Lv.1',
'稀有Lv.2',
'稀有Lv.3',
'稀有Lv.4',
'稀有Lv.5',
'神器Lv.1',
'神器Lv.2',
'神器Lv.3',
'神器Lv.4',
'神器Lv.5',
'传说Lv.1',
'传说Lv.2',
'传说Lv.3',
'传说Lv.4',
'传说Lv.5',
'史诗Lv.1',
]
const emblemMaxCount = computed(() => {
if (part.value == '彩色') {
return 6
}
if (part.value == '白金') {
return 2
}
return 4
})
const emblemGridStyle = computed(() => {
return {
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
}
})
const emblemItemStyle = (index: number) => {
const count = emblemMaxCount.value
const remainder = count % 2
if (remainder === 1 && index === count - 1) {
return {
gridColumn: 'span 2 / span 2',
}
}
return {
gridColumn: 'span 1 / span 1',
}
}
const currentInfo = function <T>(name: string, defaultValue?: T) {
return computed<string | number>({
get() {
return (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(configStore.config.emblems?.[part.value] as Record<string, any>)?.[name] ??
defaultValue ??
0
)
},
set(val) {
if (val == undefined) {
return
}
// if (name == 'emblem_1' && !has_emblem_1.value) {
// // configStore.setForge(part.value, name, 0)
// return
// }
if (!configStore.config.emblems[part.value]) {
configStore.config.emblems[part.value] = { ...configStore.defaultEmblemConfig }
}
if (configStore.config.emblems[part.value]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(configStore.config.emblems[part.value] as Record<string, any>)[name] = val
}
if (part.value == '彩色' && name == 'lv') {
// 同步其他部位的等级
const lv = val as number
for (const p of ['红色', '蓝色', '黄色', '绿色']) {
if (!configStore.config.emblems[p]) {
configStore.config.emblems[p] = { ...configStore.defaultEmblemConfig }
}
if (configStore.config.emblems[p]) {
configStore.config.emblems[p]['lv'] = Math.max(
configStore.config.emblems[p]['lv'] || 0,
lv,
)
}
}
} else if (part.value != '彩色' && name == 'lv') {
// 同步彩色部位的等级
const lv = val as number
if (!configStore.config.emblems['彩色']) {
configStore.config.emblems['彩色'] = { ...configStore.defaultEmblemConfig }
}
if (configStore.config.emblems['彩色']) {
configStore.config.emblems['彩色']['lv'] = Math.min(
configStore.config.emblems['彩色']['lv'] || 0,
lv,
)
}
}
},
})
}
const lv = currentInfo('lv', 0)
const emblems = currentInfo<string[]>('items', ['', '', '', '', '', '']) as unknown as Ref<string[]>
</script>
@@ -6,7 +6,7 @@ import EquList from '@/components/dnf/Equipment/List/index.vue'
import { getImageURL } from '@/utils/images'
export default defineComponent({
name: 'Equip',
setup(props) {
setup() {
const configStore = useConfigStore()
const part = ref('头肩')
const can_upgrade = computed(() => {
@@ -15,14 +15,6 @@ export default defineComponent({
// const globalStore = useGlobalStore()
/** 是否有徽章 */
const has_socket = computed(
() => !['称号', '宠物', '耳环', '武器'].includes(part.value as string),
)
/** 是否有右侧徽章 */
const has_emblem_1 = computed(
() => has_socket.value && !['辅助装备', '魔法石'].includes(part.value as string),
)
/** 是否可以设置秘宝精度 */
const can_set_secret = computed(() => {
return ['武器', '辅助装备', '魔法石', '耳环'].includes(part.value as string)
@@ -40,9 +32,9 @@ export default defineComponent({
.sort((a, b) => (b.fame ?? 0) - (a.fame ?? 0))
})
const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
})
// const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
// return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
// })
const global_change = ref(false)
@@ -50,6 +42,7 @@ export default defineComponent({
return computed<string | number>({
get() {
return (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(configStore.config.equips?.[part.value] as Record<string, any>)?.[name] ??
defaultValue ??
0
@@ -59,10 +52,10 @@ export default defineComponent({
if (val == undefined && name !== 'weaponFusion') {
return
}
if (name == 'emblem_1' && !has_emblem_1.value) {
// configStore.setForge(part.value, name, 0)
return
}
// if (name == 'emblem_1' && !has_emblem_1.value) {
// // configStore.setForge(part.value, name, 0)
// return
// }
let parts: string[] = []
@@ -75,16 +68,16 @@ export default defineComponent({
parts = enchant.position
}
}
if (['emblem_0', 'emblem_1'].includes(name)) {
const emblem = basicInfoStore?.emblems?.find(
(item) => item.id.toString() == val.toString(),
) as IEnchantingInfo | undefined
if (emblem?.position) {
parts = emblem.position.filter(
(item) => !['皮肤', '武器装扮', '光环'].includes(item),
)
}
}
// if (['emblem_0', 'emblem_1'].includes(name)) {
// const emblem = basicInfoStore?.emblems?.find(
// (item) => item.id.toString() == val.toString(),
// ) as IEnchantingInfo | undefined
// if (emblem?.position) {
// parts = emblem.position.filter(
// (item) => !['皮肤', '武器装扮', '光环'].includes(item),
// )
// }
// }
if (['reinforceType', 'reinforce', 'adaptation'].includes(name)) {
parts = basicInfoStore.parts.filter((e) => !['称号', '宠物'].includes(e))
}
@@ -96,6 +89,7 @@ export default defineComponent({
configStore.config.equips[part] = { ...configStore.defaultEqusConfig }
}
if (configStore.config.equips[part]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(configStore.config.equips[part] as Record<string, any>)[name] = val
}
}
@@ -106,11 +100,11 @@ export default defineComponent({
// 附魔
const enchant = currentInfo<string | number>('enchant')
// 镶嵌栏1
const emblem_0 = currentInfo<string | number>('emblem_0', 0)
// // 镶嵌栏1
// const emblem_0 = currentInfo<string | number>('emblem_0', 0)
// 镶嵌栏2
const emblem_1 = currentInfo<string | number>('emblem_1')
// // 镶嵌栏2
// const emblem_1 = currentInfo<string | number>('emblem_1')
// 增幅
const reinforce_type = currentInfo<string | number>('reinforceType', 1)
@@ -133,11 +127,11 @@ export default defineComponent({
* 同步徽章1到徽章2
* @param val
*/
function changeSocket(val: number) {
if (has_emblem_1.value) {
emblem_1.value = val
}
}
// function changeSocket(val: number) {
// if (has_emblem_1.value) {
// emblem_1.value = val
// }
// }
const setWeaponFusion = (val: number) => {
if (val == weaponFusion.value) {
@@ -240,37 +234,6 @@ export default defineComponent({
))}
</calc-select>
</div>
{has_socket.value ? (
<div class="equ-profile-item">
<div class="row-name">徽章</div>
<calc-select
onChange={changeSocket}
v-model={emblem_0.value}
class="flex-1 !h-20px"
>
<calc-option value={''}>无</calc-option>
{renderList(emblem_list.value ?? [], (item) => (
<calc-option
value={item.id}
key={item.id}
>{`${item.rarity}${item.categorize}徽章[${item.detail}]`}</calc-option>
))}
</calc-select>
{has_emblem_1.value && (
<calc-select v-model={emblem_1.value} class="flex-1 !h-20px">
<calc-option value={''}>无</calc-option>
{renderList(emblem_list.value ?? [], (item) => (
<calc-option
value={item.id}
key={item.id}
>{`${item.rarity}${item.categorize}徽章[${item.detail}]`}</calc-option>
))}
</calc-select>
)}
</div>
) : (
<div></div>
)}
{can_upgrade.value ? (
<div class="equ-profile-item">
@@ -4,7 +4,7 @@
<div class="flex gap-2 mt-5px">
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].id">
<calc-option :value="0">无</calc-option>
<calc-option v-for="(item, index) in infoStore.jades" :key="item.id" :value="item.id">
<calc-option v-for="item in infoStore.jades" :key="item.id" :value="item.id">
{{ item.name }}
</calc-option>
</calc-select>
@@ -17,7 +17,7 @@
></calc-slider> -->
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].value">
<calc-option
v-for="(item, index) in valueList(configStore.config.jades[key].id)"
v-for="item in valueList(configStore.config.jades[key].id)"
:key="item.id"
:value="item.id"
>
@@ -38,26 +38,25 @@ import { getFloat } from '@/utils'
const configStore = useConfigStore()
const infoStore = useInfoStore()
const currentJade = computed(() => {
return function (id: number = -1) {
if (id == -1) {
return undefined
} else {
const res = []
const cur = infoStore.jades?.find((e) => e.id == id) as IJade
return cur
}
}
})
// const currentJade = computed(() => {
// return function (id: number = -1) {
// if (id == -1) {
// return undefined
// } else {
// const cur = infoStore.jades?.find((e) => e.id == id) as IJade
// return cur
// }
// }
// })
const formatValue = (id: number) => {
return (value: number) => {
if (currentJade.value(id)?.unit.includes('等级')) {
return `$等级 +${value}`
}
return `${getFloat(value, 1)}${currentJade.value(id)?.unit}`
}
}
// const formatValue = (id: number) => {
// return (value: number) => {
// if (currentJade.value(id)?.unit.includes('等级')) {
// return `$等级 +${value}`
// }
// return `${getFloat(value, 1)}${currentJade.value(id)?.unit}`
// }
// }
const valueList = computed(() => {
return function (id: number = -1) {
@@ -0,0 +1,77 @@
<template>
<div class="h-150px flex justify-center items-center">
<Oath v-model:part="part" :withBg="false" />
</div>
<div class="flex flex-wrap equ-profile">
<div class="equ-profile-item">
<div class="mr-10px row-name">当前部位</div>
<span>{{ part == '11' ? '誓约' : `星蕴石-${parseInt(part) + 1}` }}</span>
<calc-checkbox
style="margin-left: auto"
v-model="global_change"
label="全局修改"
></calc-checkbox>
</div>
<div class="equ-profile-item">
<div class="row-name">调适</div>
<calc-select v-model="adaptation" class="flex-1 !h-20px">
<template v-for="i in Array.from({ length: 4 }, (_, i) => i)" :key="i">
<calc-option :value="i">{{ i }}</calc-option>
</template>
</calc-select>
</div>
</div>
</template>
<script lang="ts" setup name="Oaths">
import Oath from '@/components/dnf/Equipment/Oath/index.vue'
import { useConfigStore, useInfoStore } from '@/stores'
const part = ref('0')
const global_change = ref(false)
const configStore = useConfigStore()
const infoStore = useInfoStore()
const currentInfo = function <T>(name: string, defaultValue?: T) {
return computed<string | number>({
get() {
return (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(configStore.config.oaths?.[part.value] as Record<string, any>)?.[name] ?? defaultValue ?? 0
)
},
set(val) {
if (val == undefined) {
return
}
// if (name == 'emblem_1' && !has_emblem_1.value) {
// // configStore.setForge(part.value, name, 0)
// return
// }
let parts: string[] = []
if (global_change.value) {
parts = infoStore.oathParts
} else {
parts = [part.value]
}
for (const part of parts) {
if (!configStore.config.oaths[part]) {
configStore.config.oaths[part] = { ...configStore.defaultOathConfig }
}
if (configStore.config.oaths[part]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(configStore.config.oaths[part] as Record<string, any>)[name] = val
}
}
},
})
}
// 调适
const adaptation = currentInfo<string | number>('adaptation')
</script>
@@ -12,7 +12,7 @@
class="flex-1 !h-20px"
/>
</div>
<div class="equ-profile-item">
<!-- <div class="equ-profile-item">
<div class="row-name">勋章</div>
<calc-select
v-for="(key, index) in ['medal_rarity', 'medal_reinforce']"
@@ -37,7 +37,7 @@
{{ item.name }}
</calc-option>
</calc-select>
</div>
</div> -->
<div class="equ-profile-item">
<div class="row-name">冒险团</div>
<calc-slider
+9 -3
View File
@@ -1,12 +1,16 @@
<template>
<div class="w-420px overflow-auto">
<calc-collapse class="w-100%" title="装备/时装打造(点击部位切换)">
<calc-collapse class="w-100%" title="装备/时装/徽章打造(点击部位切换)">
<calc-tabs v-model="partTab" class="!mb-2">
<calc-tab :value="0">装备栏</calc-tab>
<calc-tab :value="1">时装栏</calc-tab>
<calc-tab :value="0">装备</calc-tab>
<calc-tab :value="2">誓约</calc-tab>
<calc-tab :value="1">时装</calc-tab>
<calc-tab :value="3">徽章</calc-tab>
</calc-tabs>
<equipment v-if="partTab === 0"></equipment>
<clothes v-if="partTab === 1"></clothes>
<oaths v-if="partTab === 2"></oaths>
<emblems v-if="partTab === 3"></emblems>
</calc-collapse>
<calc-collapse class="w-100%" title="辟邪玉">
<jade></jade>
@@ -19,6 +23,8 @@
<script setup lang="ts">
import equipment from './components/Equipments.vue'
import clothes from './components/Avatars.vue'
import emblems from './components/Emblems.vue'
import oaths from './components/Oaths.vue'
import jade from './components/Jades.vue'
import sundry from './components/Sundry.vue'
const partTab = ref(0)
+60 -2
View File
@@ -5,11 +5,30 @@
<calc-button class="flex-1" @click="setStandard">设为基准</calc-button>
<calc-button class="flex-1" @click="clearStandard">清空基准</calc-button>
</div>
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold z-10">
<div
class="flex-1 text-15px flex items-center justify-center"
@click="chooseCategory('equ')"
:class="{ active: type === 'equ' }"
>
装备
</div>
<div class="w-auto text-18px flex items-center justify-center !font-normal">|</div>
<div
class="flex-1 text-15px flex items-center justify-center"
@click="chooseCategory('oath')"
:class="{ active: type === 'oath' }"
>
誓约
</div>
</div>
<Info
:suits="configStore.result.state.suits"
:info="configStore.result.state.info"
:equs="configStore.config.equips"
:avatar="configStore.config.avatarLink"
:oaths="configStore.config.oaths"
:display="type"
></Info>
</div>
<Skill
@@ -31,10 +50,10 @@ import { useConfigStore, useInfoStore } from '@/stores'
import Info from './components/Info.vue'
import Skill from './components/Skill.vue'
import BufferSkill from './components/BufferSkill.vue'
import type { IResultSkill } from '@/api/calc/type'
const configStore = useConfigStore()
const infoStore = useInfoStore()
const type = ref<'equ' | 'oath'>('equ')
const setStandard = () => {
if (!configStore?.result?.state?.uuid) return
@@ -44,6 +63,45 @@ const setStandard = () => {
const clearStandard = () => {
infoStore.setStandard(undefined)
}
const chooseCategory = (cat: 'equ' | 'oath') => {
type.value = cat
console.log(type.value)
}
</script>
<style scoped lang="scss"></style>
<style lang="scss" scoped>
.active {
color: #ccc088;
position: relative;
&::before {
content: '';
position: absolute;
width: 60px;
height: 15px;
opacity: 0.35;
background: #bf8d20;
filter: blur(7px);
z-index: 2;
}
&::after {
content: '';
position: absolute;
background: url('@/assets/img/active.png') no-repeat;
width: 148px;
height: 16px;
bottom: 2px;
transform: translate(0, 50%);
z-index: 4;
}
}
.item-head {
background: linear-gradient(#2b2817, #171407);
border-top: 1px solid #423d2c;
border-bottom: 1px solid #211d15;
text-align: center;
}
</style>
@@ -5,7 +5,9 @@
:detail="props.equs"
:with-sub-weapon="(infoStore.infos?.subweapons?.length ?? 0) > 0"
:avatar="props.avatar"
v-show="display == 'equ'"
></EquList>
<Oath :detail="props.oaths" v-show="display == 'oath'"></Oath>
</div>
<div class="flex flex-wrap border-y-1px my-1px border-y-solid border-#FFFF/10 gap-1% px-1%">
<template v-for="info in props.info" :key="info.id">
@@ -37,37 +39,71 @@
<div class="border-y-1px border-y-solid my-1px border-#FFFF/10">
<div class="flex">
<div class="flex-1 flex items-center justify-center bg-#221A08 text-#9A7E4A">
穿戴装备信息
穿戴{{ display == 'equ' ? '装备' : '誓约' }}信息
</div>
</div>
<template v-for="part in partOrder" :key="part">
<div class="flex p-2px partItem gap-5px">
<img
:src="getImageURL(`/equipment/part/${part}.png`)"
alt=""
class="w-20px h-20px object-contain"
/>
<div class="flex items-center justify-center" v-if="adaptation(part) > 0">
<template v-if="display == 'equ'">
<template v-for="part in partOrder" :key="part">
<div class="flex p-2px partItem gap-5px">
<img
:src="getImageURL(`/equipment/adaptation/${adaptation(part)}.png`)"
:src="getImageURL(`/equipment/part/${part}.png`)"
alt=""
class="w-11px h-10px object-contain"
class="w-20px h-20px object-contain"
/>
<div class="flex items-center justify-center" v-if="adaptation(part) > 0">
<img
:src="getImageURL(`/equipment/adaptation/${adaptation(part)}.png`)"
alt=""
class="w-11px h-10px object-contain"
/>
</div>
<div
class="flex items-center"
v-if="props.equs[part]?.reinforce > 0"
:class="props.equs[part]?.reinforceType == 1 ? 'artifact' : 'advanced'"
>
+{{ props.equs[part]?.reinforce }}
</div>
<div class="rare" v-if="props.equs[part]?.refine > 0">
({{ props.equs[part]?.refine }})
</div>
<div :class="rarityClass(equ(props.equs?.[part]?.id)?.rarity ?? '')">
{{ equ(props.equs?.[part]?.id)?.name }}
</div>
</div>
<div
class="flex items-center"
v-if="props.equs[part]?.reinforce > 0"
:class="props.equs[part]?.reinforceType == 1 ? 'artifact' : 'advanced'"
>
+{{ props.equs[part]?.reinforce }}
</template>
</template>
<template v-else>
<template v-for="part in oathOrder" :key="part">
<div class="flex p-2px partItem gap-5px">
<img
:src="getImageURL(`/equipment/part/${part == '11' ? '誓约' : '星蕴石'}.png`)"
alt=""
class="w-20px h-20px object-contain"
/>
<div class="flex items-center justify-center" v-if="oathAdaptation(part) > 0">
<img
:src="getImageURL(`/equipment/adaptation/${oathAdaptation(part)}.png`)"
alt=""
class="w-11px h-10px object-contain"
/>
</div>
<!-- <div
class="flex items-center"
v-if="props.equs[part]?.reinforce > 0"
:class="props.equs[part]?.reinforceType == 1 ? 'artifact' : 'advanced'"
>
+{{ props.equs[part]?.reinforce }}
</div> -->
<!-- <div class="rare" v-if="props.equs[part]?.refine > 0">
({{ props.equs[part]?.refine }})
</div> -->
{{ equ(props.equs?.[part]?.id) }}
<div :class="rarityClass(equ(props.oaths?.[part]?.id)?.rarity ?? '')">
{{ equ(props.oaths?.[part]?.id)?.name }}
</div>
</div>
<div class="rare" v-if="props.equs[part]?.refine > 0">
({{ props.equs[part]?.refine }})
</div>
<div :class="rarityClass(equ(props.equs?.[part]?.id)?.rarity ?? '')">
{{ equ(props.equs?.[part]?.id)?.name }}
</div>
</div>
</template>
</template>
</div>
<div class="h-40px w-full"></div>
@@ -79,14 +115,17 @@ import { getImageURL } from '@/utils/images'
import { rarityClass } from '@/utils'
import type { IResultUserInfo, IResultSuit } from '@/api/calc/type'
import EquList from '@/components/dnf/Equipment/List/index.vue'
import type { IConfigEquip } from '@/stores/config'
import Oath from '@/components/dnf/Equipment/Oath/index.vue'
import type { IConfigEquip, IConfigOath } from '@/stores/config'
import { useInfoStore } from '@/stores'
const props = defineProps<{
info: IResultUserInfo[]
suits: IResultSuit[]
equs: Record<string, IConfigEquip>
oaths: Record<string, IConfigOath>
avatar: string
display: 'oath' | 'equ'
}>()
const partOrder = [
@@ -103,9 +142,15 @@ const partOrder = [
'魔法石',
'耳环',
]
const oathOrder = Array.from({ length: 12 }, (_, i) => (11 - i).toString())
const infoStore = useInfoStore()
const equ = (id: string) => {
if (props.display == 'oath') {
console.log(props.oaths, id)
return infoStore.oaths.find((e) => e.id == id)
}
return infoStore.equips.find((e) => e.id == id)
}
@@ -116,6 +161,13 @@ const adaptation = (part: string) => {
)
}
const oathAdaptation = (part: string) => {
return Math.min(
equ(props.oaths?.[part]?.id)?.max_adaptation ?? 0,
props.oaths[part]?.adaptation ?? 0,
)
}
const lvFormat = (lv: number, rarity: string) => {
if (rarity == '太初') return ''
switch (lv) {
+18 -2
View File
@@ -9,8 +9,24 @@
<script lang="ts" name="index" setup>
const zoom = computed(() => {
const DPI = Math.max(0.85,Math.min(/macintosh|mac os x/i.test(navigator.userAgent.toLowerCase()) ? 1 : window.devicePixelRatio || 1,2)) * 0.95
return Math.max(1,/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent || navigator.vendor || (window as any).opera) ? 1 :(window.screen.width / 1800 / DPI))
const DPI =
Math.max(
0.85,
Math.min(
/macintosh|mac os x/i.test(navigator.userAgent.toLowerCase())
? 1
: window.devicePixelRatio || 1,
2,
),
) * 0.95
return Math.max(
1,
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
navigator.userAgent || navigator.vendor || (window as any).opera,
)
? 1
: window.screen.width / 1800 / DPI,
)
})
import { useDialog } from '@/components/hooks/dialog'
+43 -5
View File
@@ -31,9 +31,20 @@ export interface IConfigEquip {
weaponFusion: number
}
export interface IConfigOath {
id: string
adaptation: number
}
export interface IConfigEmblem {
lv: number
items: string[]
}
export interface IConfig {
skills: Record<string, { lv: number ,vp:number, up: number}>
skills: Record<string, { lv: number; vp: number; up: number }>
equips: Record<string, IConfigEquip>
oaths: Record<string, IConfigOath>
jades: Record<
string,
{
@@ -59,6 +70,7 @@ export interface IConfig {
BUFF: boolean
options: Record<string, number>
avatarLink: string
emblems: Record<string, IConfigEmblem>
}
const defaultEqusConfig = {
@@ -84,18 +96,30 @@ const defaultAvatarConfig = {
option: '0',
}
const defaultOathConfig = {
id: '',
adaptation: 0,
}
const defaultEmblemConfig = {
lv: 0,
items: ['', '', '', '', '', ''],
}
export const useConfigStore = defineStore('configStore', () => {
const config = ref<IConfig>({
skills: {},
equips: {},
oaths: {},
jades: {},
avatar: {},
sundry: {},
bindAwake: 50,
DSB: false,
BUFF: false,
options:{},
options: {},
avatarLink: '',
emblems: {},
})
const skillCountConfig = ref<IResultSkillCount[]>([])
@@ -114,19 +138,22 @@ export const useConfigStore = defineStore('configStore', () => {
skillCountConfig.value = JSON.parse(localSkillConfig)
}
}
if (!config.value)
if (!config.value) {
config.value = {
skills: {},
equips: {},
oaths: {},
jades: {},
avatar: {},
sundry: {},
bindAwake: 50,
DSB: false,
BUFF: false,
options:{},
options: {},
avatarLink: '',
emblems: {},
}
}
if (!config.value?.equips) config.value.equips = {}
if (!config.value?.skills) config.value.skills = {}
if (!config.value?.jades) config.value.jades = {}
@@ -134,6 +161,8 @@ export const useConfigStore = defineStore('configStore', () => {
if (!config.value?.sundry) config.value.sundry = {}
if (!config.value?.bindAwake) config.value.bindAwake = 50
if (!config.value?.options) config.value.options = {}
if (!config.value?.oaths) config.value.oaths = {}
if (!config.value?.emblems) config.value.emblems = {}
const part = [...infoStore.parts, '宠物', '副武器'].filter(
(a) => !config.value?.equips.hasOwnProperty(a),
@@ -156,8 +185,15 @@ export const useConfigStore = defineStore('configStore', () => {
config.value && (config.value.avatar[p] = { ...defaultAvatarConfig })
})
infoStore.oathParts.forEach((p) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
config.value &&
!config.value?.oaths.hasOwnProperty(p) &&
(config.value.oaths[p.toString()] = { ...defaultOathConfig })
})
infoStore.options.forEach((option) => {
if(Object.keys(config.value.options).includes(option.id.toString())) return
if (Object.keys(config.value.options).includes(option.id.toString())) return
config.value.options[option.id.toString()] = 0
})
@@ -238,5 +274,7 @@ export const useConfigStore = defineStore('configStore', () => {
skillCountConfig,
defaultEqusConfig,
defaultAvatarConfig,
defaultOathConfig,
defaultEmblemConfig,
}
})
+80 -5
View File
@@ -1,5 +1,5 @@
import api from '@/api'
import type { ICharacterInfo } from '@/api/info/type'
import type { ICharacterInfo, IEquipment, ISuit } from '@/api/info/type'
import { defineStore } from 'pinia'
import { useConfigStore } from './config'
import type { IResult, IResultSkill, IResultSkillCount } from '@/api/calc/type'
@@ -8,6 +8,15 @@ export const useInfoStore = defineStore('infoStore', () => {
const alter_token = ref<string>('')
const infos = ref<ICharacterInfo | null>(null)
const oathCache = ref<
{
suitId: string
skillId: string
rarity: string
data: ISuit
}[]
>([])
const skills = computed(() => infos.value?.skills ?? [])
const equips = computed(() => infos.value?.equips ?? [])
@@ -18,7 +27,7 @@ export const useInfoStore = defineStore('infoStore', () => {
const emblems = computed(() => infos.value?.emblems ?? [])
const stones = computed(() => infos.value?.stones ?? [])
// const stones = computed(() => infos.value?.stones ?? [])
const avatars = computed(() => infos.value?.avatar ?? {})
@@ -28,6 +37,62 @@ export const useInfoStore = defineStore('infoStore', () => {
const options = computed(() => infos.value?.options ?? [])
const oaths = computed(() => {
const origin = infos.value?.oaths ?? []
const result: IEquipment[] = []
origin.forEach((o) => {
o.position.forEach((p, i) => {
result.push({
...o,
oathPos: p,
displayUrl: o.wearUrl?.[parseInt(p)]
? `/equipment/icon/oathprimer/primer_equip/${o.wearUrl?.[parseInt(p)]}`
: o.imageUrl,
})
})
})
return result
})
const getOathInfo = async (suitId: string, skillId: string, rarity: string) => {
const res = oathCache.value.find(
(o) => o.suitId === suitId && o.skillId === skillId && o.rarity === rarity,
)
if (res) {
return {
...res.data,
}
}
const data: ISuit = await api.getOathInfo(suitId, skillId, rarity)
oathCache.value.push({ suitId, skillId, rarity, data })
return {
...data,
}
}
const oathSkills = computed(() => {
const origin = infos.value?.oaths_skills
if (!origin) return origin
const skillOrder = ['2', '1', '3']
return Object.fromEntries(
Object.entries(origin).map(([key, value]) => [
key,
{
...value,
skills: [...value.skills].sort((left, right) => {
const leftIndex = skillOrder.indexOf(left.skillId)
const rightIndex = skillOrder.indexOf(right.skillId)
const normalizedLeftIndex = leftIndex === -1 ? skillOrder.length : leftIndex
const normalizedRightIndex = rightIndex === -1 ? skillOrder.length : rightIndex
return normalizedLeftIndex - normalizedRightIndex
}),
},
]),
)
})
const standardUUID = ref<string>()
const setStandard = (
@@ -39,7 +104,9 @@ export const useInfoStore = defineStore('infoStore', () => {
}[],
) => {
if (!result) {
!!standardUUID.value && sessionStorage.removeItem(standardUUID.value)
if (!!standardUUID.value) {
sessionStorage.removeItem(standardUUID.value)
}
standardUUID.value = undefined
} else {
standardUUID.value = result.uuid
@@ -89,6 +156,10 @@ export const useInfoStore = defineStore('infoStore', () => {
'宠物装备-蓝',
]
const oathParts = Array.from({ length: 12 }, (_, i) => i.toString())
const emblemParts = ['彩色', '红色', '蓝色', '绿色', '黄色', '白金']
const createCharacter = async (alter: string, equVersion?: string) => {
const time = new Date().getTime()
alter_token.value = btoa(JSON.stringify({ alter, equVersion, time }))
@@ -106,13 +177,17 @@ export const useInfoStore = defineStore('infoStore', () => {
enchants,
emblems,
parts,
stones,
oaths,
avatarParts,
oathParts,
emblemParts,
avatars,
jades,
setStandard,
standard,
sundries,
options
options,
oathSkills,
getOathInfo,
}
})
+32
View File
@@ -0,0 +1,32 @@
export function resolveTeleportTarget(target?: string | Element | null) {
if (typeof window === 'undefined') {
return null
}
if (typeof target === 'string') {
return document.querySelector(target)
}
return target ?? document.body
}
export function getElementZoom(target?: string | Element | null) {
if (typeof window === 'undefined') {
return 1
}
let zoom = 1
let current = resolveTeleportTarget(target) as HTMLElement | null
while (current) {
const currentZoom = Number.parseFloat(window.getComputedStyle(current).zoom)
if (Number.isFinite(currentZoom) && currentZoom > 0) {
zoom *= currentZoom
}
current = current.parentElement
}
return zoom || 1
}
+8
View File
@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+39
View File
@@ -0,0 +1,39 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/
# Vite
*.timestamp-*-*.mjs
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": false,
"singleQuote": true
}
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
"env": {
"browser": true
},
"categories": {
"correctness": "error"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"oxc.oxc-vscode"
]
}
+48
View File
@@ -0,0 +1,48 @@
# frontendNew
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

Some files were not shown because too many files have changed in this diff Show More