@@ -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
|
||||
|
||||
@@ -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,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"
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 * 武器成长系数[品质] * 武器锻造系数[锻造等级])
|
||||
|
||||
|
||||
武器冷却惩罚系数 = {
|
||||
|
||||
@@ -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
|
||||
"""锻造等级"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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', [])
|
||||
],
|
||||
}
|
||||
# 处理装备
|
||||
# 处理时装
|
||||
|
||||
@@ -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,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')
|
||||
|
||||
|
||||
@@ -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. 最后逐职业迁技能公式与计算逻辑。
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface IResultSkill {
|
||||
}
|
||||
|
||||
export interface IResultUserInfo {
|
||||
id: number
|
||||
name: string
|
||||
value: string | number
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 119 B |
|
After Width: | Height: | Size: 132 B |
|
After Width: | Height: | Size: 118 B |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"categories": {
|
||||
"correctness": "error"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"oxc.oxc-vscode"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||