UPDATE: New monster data
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m20s

This commit is contained in:
2026-03-17 00:45:23 +07:00
parent d27b96d023
commit 319ad79233
125 changed files with 2718 additions and 917593 deletions

BIN
data/as.json.br Normal file

Binary file not shown.

BIN
data/avatar.json.br Normal file

Binary file not shown.

View File

@@ -1,4 +1,12 @@
[ [
{
"version": "4.0.6",
"date": "17/03/2026",
"type": "update",
"items": [
"New monster data"
]
},
{ {
"version": "4.0.5", "version": "4.0.5",
"date": "09/02/2026", "date": "09/02/2026",

BIN
data/lightcone.json.br Normal file

Binary file not shown.

BIN
data/metadata.json.br Normal file

Binary file not shown.

BIN
data/moc.json.br Normal file

Binary file not shown.

BIN
data/monster.json.br Normal file

Binary file not shown.

BIN
data/peak.json.br Normal file

Binary file not shown.

BIN
data/pf.json.br Normal file

Binary file not shown.

BIN
data/relic.json.br Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,189 +0,0 @@
[
{
"id": "3001",
"begin": "2024-05-06 04:00:02",
"end": "2024-07-29 04:00:00",
"lang": {
"en": "Stormwind Knight",
"cn": "冽风骑士",
"jp": "凛冽たる風の騎士",
"kr": "한풍의 기사"
}
},
{
"id": "3002",
"begin": "2024-06-17 04:00:00",
"end": "2024-09-06 04:00:00",
"lang": {
"en": "Dominated Evils",
"cn": "支配恶兽",
"jp": "悪獣の支配",
"kr": "괴수 지배"
}
},
{
"id": "3003",
"begin": "2024-07-29 04:00:02",
"end": "2024-10-21 04:00:00",
"lang": {
"en": "Gamer's Instigation",
"cn": "煽动博弈",
"jp": "博打煽動",
"kr": "도박과 선동"
}
},
{
"id": "3004",
"begin": "2024-09-06 04:00:02",
"end": "2024-12-02 04:00:00",
"lang": {
"en": "Sovereign Control",
"cn": "支配指挥",
"jp": "指揮の支配",
"kr": "지휘 지배"
}
},
{
"id": "3005",
"begin": "2024-10-21 04:00:02",
"end": "2025-01-13 04:00:00",
"lang": {
"en": "Gusty Primate",
"cn": "冽风猢狲",
"jp": "凛冽たる風のサル",
"kr": "한풍의 원숭이"
}
},
{
"id": "3006",
"begin": "2024-12-02 04:00:02",
"end": "2025-02-24 04:00:00",
"lang": {
"en": "Locust's Instigation",
"cn": "煽动螟蝗",
"jp": "扇動螟蝗",
"kr": "선동과 해충"
}
},
{
"id": "3007",
"begin": "2025-01-12 04:00:02",
"end": "2025-04-07 04:00:00",
"lang": {
"en": "Gambling Primate",
"cn": "猢狲博弈",
"jp": "博打打ちのサル",
"kr": "도박과 원숭이"
}
},
{
"id": "3008",
"begin": "2025-02-24 04:00:02",
"end": "2025-05-19 04:00:00",
"lang": {
"en": "Warlord of the Locusts",
"cn": "螟蝗战首",
"jp": "螟蝗戦首",
"kr": "해충과 수장"
}
},
{
"id": "3009",
"begin": "2025-04-07 04:00:02",
"end": "2025-06-30 04:00:00",
"lang": {
"en": "Cutting Mistral",
"cn": "冽风支配",
"jp": "凛冽たる風の支配",
"kr": "한풍과 지배"
}
},
{
"id": "3010",
"begin": "2025-05-19 04:00:02",
"end": "2025-08-11 04:00:00",
"lang": {
"en": "Ichor Beast",
"cn": "金血恶兽",
"jp": "黄金の血の悪獣",
"kr": "황금 피와 괴수"
}
},
{
"id": "3011",
"begin": "2025-06-30 04:00:02",
"end": "2025-09-22 04:00:00",
"lang": {
"en": "Lupine Warhead",
"cn": "天狼战首",
"jp": "天狼戦首",
"kr": "천랑과 수장"
}
},
{
"id": "3012",
"begin": "2025-08-11 04:00:02",
"end": "2025-11-03 04:00:00",
"lang": {
"en": "Gale of Netherveil",
"cn": "冥茫冽风",
"jp": "苛烈なる寒風",
"kr": "어둠과 한풍"
}
},
{
"id": "3013",
"begin": "2025-09-22 04:00:02",
"end": "2025-12-15 04:00:00",
"lang": {
"en": "Instigation of the Locusts",
"cn": "螟蝗煽动",
"jp": "螟蝗の扇動",
"kr": "해충과 선동"
}
},
{
"id": "3014",
"begin": "2025-11-03 04:00:02",
"end": "2025-12-15 04:00:00",
"lang": {
"en": "Primate Knight",
"cn": "猢狲骑士",
"jp": "サルとナイト",
"kr": "원숭이와 기사"
}
},
{
"id": "3015",
"begin": "2025-12-15 04:00:00",
"end": "2026-02-09 04:00:00",
"lang": {
"en": "Dominance of Netherveil",
"cn": "支配冥茫",
"jp": "幽暗の支配者",
"kr": "지배와 어둠"
}
},
{
"id": "3016",
"begin": "2026-01-05 04:00:02",
"end": "2026-03-16 04:00:00",
"lang": {
"en": "Militant Lupine",
"cn": "兵锋天狼",
"jp": "兵鋒天狼",
"kr": "창날과 천랑"
}
},
{
"id": "3017",
"begin": "2026-03-16 04:00:00",
"end": "2026-05-16 04:00:00",
"lang": {
"en": "Militant Lupine",
"cn": "兵锋天狼",
"jp": "兵鋒天狼",
"kr": "창날과 천랑"
}
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,762 +0,0 @@
{
"21": {
"1": {
"property": "HPDelta",
"base": 45.15840148925781,
"step": 15.805439949035645,
"step_num": null
}
},
"22": {
"1": {
"property": "AttackDelta",
"base": 22.579200744628903,
"step": 7.902719974517822,
"step_num": null
}
},
"23": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.020736001431941983,
"step": 0.00725799985229969,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.02211800031363964,
"step": 0.0077410005033016205,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
}
},
"24": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 1.6128000020980835,
"step": 1.0,
"step_num": null
}
},
"25": {
"1": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
}
},
"26": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.012442000210285189,
"step": 0.004355000331997871,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.009677000343799593,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.012096000835299492,
"step_num": null
}
},
"31": {
"1": {
"property": "HPDelta",
"base": 67.73760223388672,
"step": 23.708160400390625,
"step_num": null
}
},
"32": {
"1": {
"property": "AttackDelta",
"base": 33.86880111694336,
"step": 11.854080200195312,
"step_num": null
}
},
"33": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.03110400028526783,
"step": 0.010886001400649548,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.03317800164222717,
"step": 0.011611999943852425,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
}
},
"34": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 2.4191999435424805,
"step": 1.0,
"step_num": null
}
},
"35": {
"1": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.037324998527765274,
"step": 0.013064000755548475,
"step_num": null
}
},
"36": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.018662000074982643,
"step": 0.006532000377774239,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
}
},
"41": {
"1": {
"property": "HPDelta",
"base": 90.31680297851562,
"step": 31.61087989807129,
"step_num": null
}
},
"42": {
"1": {
"property": "AttackDelta",
"base": 45.15840148925781,
"step": 15.805439949035645,
"step_num": null
}
},
"43": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.08294399827718735,
"step": 0.029029998928308487,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.04423699900507927,
"step": 0.015483000315725803,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
}
},
"44": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 3.225600004196167,
"step": 1.100000023841858,
"step_num": null
}
},
"45": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.04976600036025047,
"step": 0.01741800084710121,
"step_num": null
}
},
"46": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.08294399827718735,
"step": 0.029029998928308487,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.024883000180125237,
"step": 0.008709000423550606,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"51": {
"1": {
"property": "HPDelta",
"base": 112.89600372314452,
"step": 39.51359939575195,
"step_num": null
}
},
"52": {
"1": {
"property": "AttackDelta",
"base": 56.448001861572266,
"step": 19.756799697875977,
"step_num": null
}
},
"53": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "CriticalChanceBase",
"base": 0.05183999985456467,
"step": 0.018144000321626663,
"step_num": null
},
"5": {
"property": "CriticalDamageBase",
"base": 0.10367999970912932,
"step": 0.036288000643253326,
"step_num": null
},
"6": {
"property": "HealRatioBase",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
},
"7": {
"property": "StatusProbabilityBase",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"54": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "SpeedDelta",
"base": 4.0320000648498535,
"step": 1.399999976158142,
"step_num": null
}
},
"55": {
"1": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"2": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"3": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
},
"4": {
"property": "PhysicalAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"5": {
"property": "FireAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"6": {
"property": "IceAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"7": {
"property": "ThunderAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"8": {
"property": "WindAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"9": {
"property": "QuantumAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
},
"10": {
"property": "ImaginaryAddedRatio",
"base": 0.06220800057053566,
"step": 0.021773001179099083,
"step_num": null
}
},
"56": {
"1": {
"property": "BreakDamageAddedRatioBase",
"base": 0.10367999970912932,
"step": 0.036288000643253326,
"step_num": null
},
"2": {
"property": "SPRatioBase",
"base": 0.03110400028526783,
"step": 0.010886001400649548,
"step_num": null
},
"3": {
"property": "HPAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"4": {
"property": "AttackAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
},
"5": {
"property": "DefenceAddedRatio",
"base": 0.08640000224113464,
"step": 0.030240001156926155,
"step_num": null
}
},
"433": {
"1": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
},
"434": {
"1": {
"property": "CriticalChanceBase",
"base": 0.041471999138593674,
"step": 0.014515000395476818,
"step_num": null
}
},
"436": {
"1": {
"property": "HealRatioBase",
"base": 0.04423699900507927,
"step": 0.015483000315725803,
"step_num": null
}
},
"441": {
"1": {
"property": "HPAddedRatio",
"base": 0.055296000093221664,
"step": 0.019354000687599186,
"step_num": null
}
},
"443": {
"1": {
"property": "DefenceAddedRatio",
"base": 0.06911999732255936,
"step": 0.024191999807953835,
"step_num": null
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,574 +0,0 @@
[
{
"id": "100",
"begin": "",
"end": "",
"lang": {
"en": "The Last Vestiges of Towering Citadel",
"cn": "永屹之城遗秘",
"jp": "永屹の城の秘密",
"kr": "영원히 굳건한 도시에 남겨진 비밀"
}
},
{
"id": "900",
"begin": "",
"end": "",
"lang": {
"en": "The Voyage of Navis Astriger",
"cn": "天艟求仙迷航录",
"jp": "天艟求仙放浪記",
"kr": "불사의 약 원정기"
}
},
{
"id": "101",
"begin": "2023-02-06 04:00:00",
"end": "2023-03-06 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "102",
"begin": "2022-11-14 04:00:00",
"end": "2022-11-28 04:00:00",
"lang": {
"en": "Frostscar Reverie",
"cn": "霜痕旧梦",
"jp": "霜の跡に旧夢",
"kr": "서리 내린 꿈"
}
},
{
"id": "103",
"begin": "2022-11-28 04:00:00",
"end": "2022-12-12 00:00:00",
"lang": {
"en": "Everwinter Trials",
"cn": "永冬试炼",
"jp": "常冬の試練",
"kr": "영원한 겨울의 시련"
}
},
{
"id": "104",
"begin": "2023-03-06 04:00:00",
"end": "2023-03-20 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "105",
"begin": "2022-12-26 04:00:00",
"end": "2023-01-09 04:00:00",
"lang": {
"en": "Frostscar Reverie",
"cn": "霜痕旧梦",
"jp": "霜の跡に旧夢",
"kr": "서리 내린 꿈"
}
},
{
"id": "106",
"begin": "2023-01-09 04:00:00",
"end": "2023-01-23 04:00:00",
"lang": {
"en": "Everwinter Trials",
"cn": "永冬试炼",
"jp": "常冬の試練",
"kr": "영원한 겨울의 시련"
}
},
{
"id": "107",
"begin": "2023-03-20 04:00:00",
"end": "2023-04-03 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "108",
"begin": "2033-02-06 04:00:00",
"end": "2033-02-20 04:00:00",
"lang": {
"en": "Frostscar Reverie",
"cn": "霜痕旧梦",
"jp": "霜の跡に旧夢",
"kr": "서리 내린 꿈"
}
},
{
"id": "109",
"begin": "2033-02-20 04:00:00",
"end": "2033-03-06 04:00:00",
"lang": {
"en": "Everwinter Trials",
"cn": "永冬试炼",
"jp": "常冬の試練",
"kr": "영원한 겨울의 시련"
}
},
{
"id": "110",
"begin": "2000-06-12 04:00:00",
"end": "2000-06-26 04:00:00",
"lang": {
"en": "Coldiron Tribulation",
"cn": "寒铁砥砺",
"jp": "寒鉄練磨",
"kr": "한철 연마"
}
},
{
"id": "111",
"begin": "2000-06-26 04:00:00",
"end": "2000-07-10 04:00:00",
"lang": {
"en": "Hyperborean Search for Warmth",
"cn": "蹈冰寻火",
"jp": "氷踏みて炎求む",
"kr": "불을 찾는 얼음"
}
},
{
"id": "112",
"begin": "2000-07-10 04:00:00",
"end": "2000-07-24 04:00:00",
"lang": {
"en": "Stormquell",
"cn": "风暴止息",
"jp": "止息せし嵐",
"kr": "잦아든 폭풍"
}
},
{
"id": "113",
"begin": "2003-06-05 04:00:00",
"end": "2003-06-12 04:00:00",
"lang": {
"en": "Adrift in Astral Seas",
"cn": "孤航天海",
"jp": "天海の孤航",
"kr": "고독한 천공 항행"
}
},
{
"id": "114",
"begin": "2003-06-12 04:00:00",
"end": "2003-06-16 04:00:00",
"lang": {
"en": "Raintear Strife",
"cn": "泪雨长战",
"jp": "涙雨戦争",
"kr": "눈물의 전쟁"
}
},
{
"id": "115",
"begin": "2003-06-16 04:00:00",
"end": "2003-07-05 04:00:00",
"lang": {
"en": "Traces of Sanctus Medicus",
"cn": "药王垂迹",
"jp": "薬王の垂迹",
"kr": "약왕의 화신"
}
},
{
"id": "116",
"begin": "2023-04-03 04:00:00",
"end": "2023-04-17 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "117",
"begin": "2000-04-17 04:00:00",
"end": "2000-05-15 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "118",
"begin": "2000-05-15 04:00:00",
"end": "2000-05-29 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "119",
"begin": "2000-05-29 04:00:00",
"end": "2000-06-12 04:00:00",
"lang": {
"en": "Favor of Amber",
"cn": "琥珀恩赐",
"jp": "琥珀の賜物",
"kr": "앰버의 은혜"
}
},
{
"id": "1001",
"begin": "2000-09-04 04:00:00",
"end": "2000-09-18 04:00:00",
"lang": {
"en": "Ethereal Shipcraft",
"cn": "迷梦造舸",
"jp": "迷夢造舟",
"kr": "공상으로 만든 배"
}
},
{
"id": "1002",
"begin": "2023-07-17 04:00:00",
"end": "2023-08-21 04:00:00",
"lang": {
"en": "A Shot From the Sky",
"cn": "天裂一射",
"jp": "天裂の一射",
"kr": "하늘을 가르는 화살"
}
},
{
"id": "1003",
"begin": "2000-10-02 04:00:00",
"end": "2000-10-16 04:00:00",
"lang": {
"en": "Mara and Null",
"cn": "魔阴空劫",
"jp": "魔陰空劫",
"kr": "마각의 공겁"
}
},
{
"id": "1004",
"begin": "2000-10-16 04:00:00",
"end": "2000-10-30 04:00:00",
"lang": {
"en": "Living and Flaming Catastrophes",
"cn": "生劫火劫",
"jp": "生劫火劫",
"kr": "생겁과 화겁"
}
},
{
"id": "1005",
"begin": "2023-08-27 04:00:00",
"end": "2023-09-25 04:00:00",
"lang": {
"en": "Ambrosial Arbor's Arrival",
"cn": "建木降临",
"jp": "建木降臨",
"kr": "불멸의 거목 강림"
}
},
{
"id": "1006",
"begin": "2000-11-13 04:00:00",
"end": "2000-11-27 04:00:00",
"lang": {
"en": "Divine Root Subdual",
"cn": "镇伏玄根",
"jp": "玄根鎮伏",
"kr": "현근 제압"
}
},
{
"id": "1007",
"begin": "2000-11-27 04:00:00",
"end": "2000-12-11 04:00:00",
"lang": {
"en": "Oath of Eternal Alliance",
"cn": "万载盟誓",
"jp": "万年移ろわぬ盟約の誓い",
"kr": "영원한 맹세"
}
},
{
"id": "1008",
"begin": "2023-09-25 04:00:00",
"end": "2023-11-13 04:00:00",
"lang": {
"en": "Sedition of Imbibitor Lunae",
"cn": "饮月之乱",
"jp": "飲月の乱",
"kr": "음월의 난"
}
},
{
"id": "1009",
"begin": "2023-10-30 04:00:03",
"end": "2023-12-24 04:00:00",
"lang": {
"en": "Enigma in Deep Space",
"cn": "藏于深空之秘",
"jp": "深空に隠された秘密",
"kr": "심우주에 숨겨진 비밀"
}
},
{
"id": "1010",
"begin": "2023-10-30 04:00:01",
"end": "2023-12-24 04:00:00",
"lang": {
"en": "Light of Reignition",
"cn": "重燃之光",
"jp": "再燃の光",
"kr": "다시 빛나는 불꽃"
}
},
{
"id": "1011",
"begin": "2023-12-04 04:00:03",
"end": "2024-02-05 04:00:00",
"lang": {
"en": "Dreamland of Longing",
"cn": "难舍梦乡",
"jp": "醒めたくない夢",
"kr": "애틋한 꿈세계"
}
},
{
"id": "1012",
"begin": "2023-12-04 04:00:01",
"end": "2024-03-27 04:00:00",
"lang": {
"en": "Eve of Wanton Feast",
"cn": "一晌荒宴",
"jp": "荒唐な宴",
"kr": "찰나의 연회"
}
},
{
"id": "1013",
"begin": "2024-02-05 04:00:00",
"end": "2024-04-24 04:00:00",
"lang": {
"en": "White Night Chronicles",
"cn": "白夜梦国记",
"jp": "白昼夢国記",
"kr": "백야몽국 연대기"
}
},
{
"id": "1014",
"begin": "2024-03-11 04:00:00",
"end": "2024-05-06 04:00:00",
"lang": {
"en": "Dream Within Dream",
"cn": "梦中之梦",
"jp": "夢の中の夢",
"kr": "꿈속의 꿈"
}
},
{
"id": "1015",
"begin": "2024-03-11 04:00:01",
"end": "2024-06-17 04:00:00",
"lang": {
"en": "A Song's True Theme (<unbreak>2.2</unbreak>)",
"cn": "弦外之声(<unbreak>2.2</unbreak>)",
"jp": "弦外の音(<unbreak>2.2</unbreak>)",
"kr": "현 외의 소리(<unbreak>2.2</unbreak>)"
}
},
{
"id": "1016",
"begin": "2024-05-06 04:00:00",
"end": "2024-07-29 04:00:00",
"lang": {
"en": "Dissipation of Dreams (<unbreak>2.3</unbreak>)",
"cn": "曲尽梦散(<unbreak>2.3</unbreak>)",
"jp": "曲は尽き夢は散る(<unbreak>2.3</unbreak>)",
"kr": "끝난 곡조와 흩어진 꿈(<unbreak>2.3</unbreak>)"
}
},
{
"id": "1017",
"begin": "2024-06-17 04:00:02",
"end": "2024-09-06 04:00:00",
"lang": {
"en": "The Big Sleep",
"cn": "长眠不醒",
"jp": "大いなる眠り",
"kr": "깨지 않는 깊은 잠"
}
},
{
"id": "1018",
"begin": "2024-07-29 04:00:00",
"end": "2024-10-21 04:00:00",
"lang": {
"en": "Scalegorge Tidalflow",
"cn": "鳞渊潮动",
"jp": "鱗淵の潮騒",
"kr": "인연의 파도"
}
},
{
"id": "1019",
"begin": "2024-09-06 04:00:00",
"end": "2024-12-02 04:00:00",
"lang": {
"en": "Dancing with the Dreams",
"cn": "与梦共舞",
"jp": "夢と踊る",
"kr": "꿈과 춤을"
}
},
{
"id": "1020",
"begin": "2024-10-21 04:00:00",
"end": "2025-01-13 04:00:00",
"lang": {
"en": "Troopship Mayhem",
"cn": "舸舰迷津",
"jp": "遭難した巨艦",
"kr": "길 잃은 거함"
}
},
{
"id": "1021",
"begin": "2024-12-02 04:00:00",
"end": "2025-02-24 04:00:00",
"lang": {
"en": "Strife of Creation",
"cn": "创世纷争",
"jp": "創世の争い",
"kr": "창세의 분쟁"
}
},
{
"id": "1022",
"begin": "2025-01-12 04:00:00",
"end": "2025-04-07 04:00:00",
"lang": {
"en": "Out of Home",
"cn": "出故乡记",
"jp": "故郷より旅立つ",
"kr": "출향기"
}
},
{
"id": "1023",
"begin": "2025-02-24 04:00:00",
"end": "2025-05-19 04:00:00",
"lang": {
"en": "Breath of the Othershore",
"cn": "彼岸之息",
"jp": "向こう岸の息吹",
"kr": "피안의 숨결"
}
},
{
"id": "1024",
"begin": "2025-04-07 04:00:00",
"end": "2025-06-30 04:00:00",
"lang": {
"en": "Lupine Moon-Devourer",
"cn": "赤月吞狼",
"jp": "紅月を呑む狼",
"kr": "늑대를 삼킨 붉은 달"
}
},
{
"id": "1025",
"begin": "2025-05-19 04:00:00",
"end": "2025-08-11 04:00:00",
"lang": {
"en": "Gambler's Plight",
"cn": "博徒困境",
"jp": "博打うちのジレンマ",
"kr": "노름꾼의 곤경"
}
},
{
"id": "1026",
"begin": "2025-06-30 04:00:00",
"end": "2025-09-22 04:00:00",
"lang": {
"en": "Pillar of Genesis",
"cn": "创世之柱",
"jp": "創世の柱",
"kr": "창세의 기둥"
}
},
{
"id": "1027",
"begin": "2025-08-11 04:00:00",
"end": "2025-11-03 04:00:00",
"lang": {
"en": "Category Mistake",
"cn": "范畴错误",
"jp": "カテゴリーエラー",
"kr": "범주 오류"
}
},
{
"id": "1028",
"begin": "2025-09-22 04:00:00",
"end": "2025-12-15 04:00:00",
"lang": {
"en": "Monkey Business",
"cn": "猴子把戏",
"jp": "サルのトリック",
"kr": "원숭이 트릭"
}
},
{
"id": "1029",
"begin": "2025-11-03 04:00:00",
"end": "2026-02-09 04:00:00",
"lang": {
"en": "Breached Nest",
"cn": "堤溃蚁穴",
"jp": "蟻の一穴",
"kr": "제방을 허무는 개미굴"
}
},
{
"id": "1030",
"begin": "2026-01-05 04:00:00",
"end": "2026-03-16 04:00:00",
"lang": {
"en": "Cyber Mystery",
"cn": "网络谜踪",
"jp": "サイバーミステリー",
"kr": "사이버 미스터리"
}
},
{
"id": "1031",
"begin": "2026-02-08 04:00:00",
"end": "2026-03-16 04:00:00",
"lang": {
"en": "Grand Finale",
"cn": "演剧终焉",
"jp": "演劇の終焉",
"kr": "연극의 종결"
}
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,57 +0,0 @@
[
{
"id": "1",
"begin": "",
"end": "",
"lang": {
"en": "Intellitron Endgame",
"cn": "智械残局",
"jp": "オムニックの終局",
"kr": "지능 기계 종반전"
}
},
{
"id": "2",
"begin": "",
"end": "",
"lang": {
"en": "Illusory Realm of the Blazing Sun",
"cn": "烈阳幻域",
"jp": "烈日の幻域",
"kr": "태양의 <unbreak>환상 영역</unbreak>"
}
},
{
"id": "3",
"begin": "",
"end": "",
"lang": {
"en": "Dissonance",
"cn": "不协和音",
"jp": "不協和音",
"kr": "불협화음"
}
},
{
"id": "4",
"begin": "",
"end": "",
"lang": {
"en": "Cyber Crisis",
"cn": "网络风波",
"jp": "ネット騒動",
"kr": "네트워크 소동"
}
},
{
"id": "5",
"begin": "",
"end": "",
"lang": {
"en": "Don't Mess With Pom-Pom",
"cn": "别惹帕姆",
"jp": "パムを怒らせるな",
"kr": "폼폼 조심"
}
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,244 +0,0 @@
[
{
"id": "2001",
"begin": "2023-10-30 04:00:04",
"end": "2023-12-24 04:00:00",
"lang": {
"en": "Youci's Wandering Words",
"cn": "游辞漫说",
"jp": "遊辞漫談",
"kr": "유사의 허황된 이야기"
}
},
{
"id": "2002",
"begin": "2023-10-30 04:00:02",
"end": "2024-02-05 04:00:00",
"lang": {
"en": "Tales of a Tethered Bird",
"cn": "羁鸟奇谭",
"jp": "籠鳥奇譚",
"kr": "새장 속 새 이야기"
}
},
{
"id": "2003",
"begin": "2023-12-04 04:00:02",
"end": "2024-02-05 04:00:00",
"lang": {
"en": "An Expression of Eloquence",
"cn": "舌灿莲花",
"jp": "優れた弁舌",
"kr": "찬란한 감언"
}
},
{
"id": "2004",
"begin": "2023-02-05 04:00:00",
"end": "2024-04-24 04:00:00",
"lang": {
"en": "Deceitful Chaos",
"cn": "撒诈捣虚",
"jp": "噓八百",
"kr": "새빨간 거짓말"
}
},
{
"id": "2005",
"begin": "2023-02-05 04:00:01",
"end": "2024-04-24 04:00:00",
"lang": {
"en": "Fictitious Wordsmithing",
"cn": "作言造语",
"jp": "口から出まかせ",
"kr": "꾸며낸 이야기"
}
},
{
"id": "2006",
"begin": "2024-03-11 04:00:00",
"end": "2024-06-17 04:00:00",
"lang": {
"en": "Lexical Enigma (<unbreak>2.2</unbreak>)",
"cn": "新词迷离(<unbreak>2.2</unbreak>)",
"jp": "あやふやな新語(<unbreak>2.2</unbreak>)",
"kr": "흐릿한 새말(<unbreak>2.2</unbreak>)"
}
},
{
"id": "2007",
"begin": "2024-05-06 04:00:01",
"end": "2024-07-29 04:00:00",
"lang": {
"en": "Out of Thin Air (<unbreak>2.3</unbreak>)",
"cn": "向壁虚造(<unbreak>2.3</unbreak>)",
"jp": "向壁虚造(<unbreak>2.3</unbreak>)",
"kr": "터무니없는 날조(<unbreak>2.3</unbreak>)"
}
},
{
"id": "2008",
"begin": "2024-06-17 04:00:01",
"end": "2024-09-06 04:00:00",
"lang": {
"en": "Words of Deceit",
"cn": "欺人虚言",
"jp": "虚言",
"kr": "거짓된 말마디"
}
},
{
"id": "2009",
"begin": "2024-07-29 04:00:01",
"end": "2024-10-21 04:00:00",
"lang": {
"en": "Volubility",
"cn": "巧言如流",
"jp": "巧言流水の如し",
"kr": "청산유수"
}
},
{
"id": "2010",
"begin": "2024-09-06 04:00:01",
"end": "2024-12-02 04:00:00",
"lang": {
"en": "Rumor Mill",
"cn": "论黄数黑",
"jp": "数黒論黄",
"kr": "허튼소리"
}
},
{
"id": "2011",
"begin": "2024-10-21 04:00:01",
"end": "2025-01-13 04:00:00",
"lang": {
"en": "Technicality Entrapment",
"cn": "深文巧诋",
"jp": "手練手管",
"kr": "중상모략"
}
},
{
"id": "2012",
"begin": "2024-12-02 04:00:01",
"end": "2025-02-24 04:00:00",
"lang": {
"en": "Clichéd Sayings",
"cn": "陈腔滥调",
"jp": "陳腐な表現",
"kr": "진부한 말"
}
},
{
"id": "2013",
"begin": "2025-01-12 04:00:01",
"end": "2025-04-07 04:00:00",
"lang": {
"en": "Self-Fulfilling Prophecy",
"cn": "自证预言",
"jp": "自己証明の預言",
"kr": "자기실현적 예언"
}
},
{
"id": "2014",
"begin": "2025-02-24 04:00:01",
"end": "2025-05-19 04:00:00",
"lang": {
"en": "Structural Rules",
"cn": "结构规律",
"jp": "構造規則",
"kr": "구조적 규율"
}
},
{
"id": "2015",
"begin": "2025-04-07 04:00:01",
"end": "2025-06-30 04:00:00",
"lang": {
"en": "Narrative Analysis",
"cn": "叙事分析",
"jp": "叙事分析",
"kr": "내러티브 분석"
}
},
{
"id": "2016",
"begin": "2025-05-19 04:00:01",
"end": "2025-08-11 04:00:00",
"lang": {
"en": "Three Act Structure",
"cn": "三幕架构",
"jp": "三幕構成",
"kr": "3막 구조"
}
},
{
"id": "2017",
"begin": "2025-06-30 04:00:01",
"end": "2025-09-22 04:00:00",
"lang": {
"en": "Subjective Narrative",
"cn": "主观叙事",
"jp": "主観的叙事",
"kr": "주관적 서사"
}
},
{
"id": "2018",
"begin": "2025-08-11 04:00:01",
"end": "2025-11-03 04:00:00",
"lang": {
"en": "Iambic Pentameter",
"cn": "五步抑扬",
"jp": "弱強五歩格",
"kr": "약강 오보격"
}
},
{
"id": "2019",
"begin": "2025-09-22 04:00:01",
"end": "2025-12-15 04:00:00",
"lang": {
"en": "Syntax Rule",
"cn": "程式句法",
"jp": "シンタックスルール",
"kr": "프로그래밍 문법"
}
},
{
"id": "2020",
"begin": "2025-11-03 04:00:01",
"end": "2026-02-09 04:00:00",
"lang": {
"en": "Epic Collection",
"cn": "史诗集群",
"jp": "叙事詩集",
"kr": "서사시 모음집"
}
},
{
"id": "2021",
"begin": "2026-01-05 04:00:01",
"end": "2026-03-16 04:00:00",
"lang": {
"en": "Wordless Novel",
"cn": "无字小说",
"jp": "ワードレスノベル",
"kr": "글자 없는 소설"
}
},
{
"id": "2022",
"begin": "2026-02-08 04:00:01",
"end": "2026-03-16 04:00:00",
"lang": {
"en": "Virtual Made Manifest",
"cn": "虚境成章",
"jp": "仮想の章節",
"kr": "허상으로 이룬 장"
}
}
]

File diff suppressed because one or more lines are too long

View File

@@ -1,698 +0,0 @@
{
"1001": [
"https://starrail.honeyhunterworld.com/img/eidolon/memory-of-you-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/memory-of-it-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/memory-of-everything-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/never-forfeit-again-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/never-forget-again-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/just-like-this-always-eidolon_icon_small.webp"
],
"1002": [
"https://starrail.honeyhunterworld.com/img/eidolon/the-higher-you-fly-the-harder-you-fall-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/quell-the-venom-octet-quench-the-vice-oflame-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/seen-and-unseen-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/roaring-dragon-and-soaring-sun-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-drop-of-rain-feeds-a-torrent-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-troubled-soul-lies-in-wait-eidolon_icon_small.webp"
],
"1003": [
"https://starrail.honeyhunterworld.com/img/eidolon/childhood-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/convergence-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/poised-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dedication-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/aspiration-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/trailblaze-eidolon_icon_small.webp"
],
"1004": [
"https://starrail.honeyhunterworld.com/img/eidolon/legacy-of-honor-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/conflux-of-stars-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/prayer-of-peace-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/appellation-of-justice-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/power-of-kindness-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/prospect-of-glory-eidolon_icon_small.webp"
],
"1005": [
"https://starrail.honeyhunterworld.com/img/eidolon/da-capo-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fortississimo-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/capriccio-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/recitativo-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/doloroso-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/leggiero-eidolon-2_icon_small.webp"
],
"1006": [
"https://starrail.honeyhunterworld.com/img/eidolon/social-engineering-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/zombie-network-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/payload-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bounce-attack-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/brute-force-attack-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/overlay-network-eidolon-2_icon_small.webp"
],
"1008": [
"https://starrail.honeyhunterworld.com/img/eidolon/to-the-bitter-end-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/breaking-free-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/power-through-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/turn-the-tables-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hammer-and-tongs-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/self-sacrifice-eidolon_icon_small.webp"
],
"1009": [
"https://starrail.honeyhunterworld.com/img/eidolon/star-sings-sans-verses-or-vocals-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/moon-speaks-in-wax-and-wane-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/meteor-showers-for-wish-and-want-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/aurora-basks-in-beauty-and-bliss-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/nebula-secludes-in-runes-and-riddles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cosmos-dreams-in-calm-and-comfort-eidolon_icon_small.webp"
],
"1013": [
"https://starrail.honeyhunterworld.com/img/eidolon/kick-you-when-youre-down-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/keep-the-ball-rolling-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/thats-the-kind-of-girl-i-am-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hit-where-it-hurts-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cuss-big-or-cuss-nothing-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/no-one-can-betray-me-eidolon_icon_small.webp"
],
"1014": [
"https://starrail.honeyhunterworld.com/img/eidolon/the-lost-white-walls-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-lost-oath-of-the-round-table-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-wish-across-fifteen-centuries-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-saga-of-sixteen-winter-days-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-dreamed-utopian-dawn-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-long-fated-night-eidolon_icon_small.webp"
],
"1015": [
"https://starrail.honeyhunterworld.com/img/eidolon/the-unreached-dream-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-unfulfilled-happiness-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-untamed-will-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-unsung-life-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-nameless-watch-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-endless-pilgrimage-eidolon_icon_small.webp"
],
"1101": [
"https://starrail.honeyhunterworld.com/img/eidolon/hone-your-strength-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/quick-march-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bombardment-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/take-by-surprise-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/unstoppable-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/piercing-rainbow-eidolon_icon_small.webp"
],
"1102": [
"https://starrail.honeyhunterworld.com/img/eidolon/extirpating-slash-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dancing-butterfly-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dazzling-tumult-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/flitting-phantasm-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/piercing-shards-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/shattering-shambles-eidolon_icon_small.webp"
],
"1103": [
"https://starrail.honeyhunterworld.com/img/eidolon/echo-chamber-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/encore-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/listen-the-heartbeat-of-the-gears-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/make-some-noise-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/belobogs-loudest-roar-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/this-song-rocks-to-heaven-eidolon_icon_small.webp"
],
"1104": [
"https://starrail.honeyhunterworld.com/img/eidolon/due-diligence-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lingering-cold-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/never-surrender-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/faith-moves-mountains-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cold-iron-fist-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/unyielding-resolve-eidolon_icon_small.webp"
],
"1105": [
"https://starrail.honeyhunterworld.com/img/eidolon/pharmacology-expertise-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/clinical-research-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-right-cure-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/miracle-cure-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/preventive-treatment-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/doctors-grace-eidolon_icon_small.webp"
],
"1106": [
"https://starrail.honeyhunterworld.com/img/eidolon/victory-report-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/adamant-charge-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/suppressive-force-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/full-analysis-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/absolute-jeopardy-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/feeble-pursuit-eidolon_icon_small.webp"
],
"1107": [
"https://starrail.honeyhunterworld.com/img/eidolon/a-tall-figure-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-tight-embrace-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cold-steel-armor-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/familys-warmth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-small-promise-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/long-company-eidolon_icon_small.webp"
],
"1108": [
"https://starrail.honeyhunterworld.com/img/eidolon/rising-love-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/infectious-enthusiasm-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/big-money-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-deeper-the-love-the-stronger-the-hate-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/huuuuge-money-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/increased-spending-eidolon_icon_small.webp"
],
"1109": [
"https://starrail.honeyhunterworld.com/img/eidolon/early-to-bed-early-to-rise-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/happy-tummy-happy-body-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dont-be-picky-nothings-icky-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/its-okay-to-not-know-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/let-the-moles-deeds-be-known-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/always-ready-to-punch-and-kick-eidolon_icon_small.webp"
],
"1110": [
"https://starrail.honeyhunterworld.com/img/eidolon/morning-of-snow-hike-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/noon-of-portable-furnace-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/afternoon-of-avalanche-beacon-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dusk-of-warm-campfire-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/night-of-aurora-tea-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dawn-of-explorers-chart-eidolon_icon_small.webp"
],
"1111": [
"https://starrail.honeyhunterworld.com/img/eidolon/fighting-endlessly-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-enemy-is-weak-i-am-strong-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/born-for-the-ring-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/never-turning-back-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-spirit-of-wildfire-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-champions-applause-eidolon_icon_small.webp"
],
"1112": [
"https://starrail.honeyhunterworld.com/img/eidolon/future-market-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bona-fide-acquisition-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/seize-the-big-and-free-the-small-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/agile-operation-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/inflationary-demand-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/incentive-mechanism-eidolon_icon_small.webp"
],
"1201": [
"https://starrail.honeyhunterworld.com/img/eidolon/rise-through-the-tiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sleep-on-the-tiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/read-between-the-tiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/right-on-the-tiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/gambit-for-the-tiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/prevail-beyond-the-tiles-eidolon_icon_small.webp"
],
"1202": [
"https://starrail.honeyhunterworld.com/img/eidolon/windfall-of-lucky-springs-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/gainfully-gives-givingly-gains-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/halcyon-bequest-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/jovial-versatility-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sauntering-coquette-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/peace-brings-wealth-to-all-eidolon_icon_small.webp"
],
"1203": [
"https://starrail.honeyhunterworld.com/img/eidolon/ablution-of-the-quick-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bestowal-from-the-pure-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/surveyal-by-the-fool-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/heavy-lies-the-crown-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cicatrix-neath-the-pain-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/reunion-with-the-dust-eidolon_icon_small.webp"
],
"1204": [
"https://starrail.honeyhunterworld.com/img/eidolon/slash-seas-split-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/swing-skies-squashed-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/strike-suns-subdued-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/spin-stars-sieged-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/stride-spoils-seized-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sweep-souls-slain-eidolon_icon_small.webp"
],
"1205": [
"https://starrail.honeyhunterworld.com/img/eidolon/blade-cuts-the-deepest-in-hell-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/ten-thousand-sorrows-from-one-broken-dream-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hardened-blade-bleeds-coldest-shade-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/rejected-by-death-infected-with-life-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/death-by-ten-lords-gaze-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/reborn-into-an-empty-husk-eidolon-2_icon_small.webp"
],
"1206": [
"https://starrail.honeyhunterworld.com/img/eidolon/cut-with-ease-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/refine-in-toil-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/rise-from-fame-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cleave-with-heart-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/prevail-via-taixu-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dwell-like-water-eidolon_icon_small.webp"
],
"1207": [
"https://starrail.honeyhunterworld.com/img/eidolon/aerial-marshal-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/skyward-command-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/torrential-fusillade-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/zephyrean-echoes-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/august-deadshot-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bowstring-thunderclap-eidolon_icon_small.webp"
],
"1208": [
"https://starrail.honeyhunterworld.com/img/eidolon/dominus-pacis-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/optimus-felix-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/apex-nexus-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fortuna-stellaris-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/arbiter-primus-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/omnia-vita-eidolon_icon_small.webp"
],
"1209": [
"https://starrail.honeyhunterworld.com/img/eidolon/svelte-saber-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/supine-serenade-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sword-savant-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/searing-sting-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/surging-strife-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/swift-swoop-eidolon_icon_small.webp"
],
"1210": [
"https://starrail.honeyhunterworld.com/img/eidolon/slurping-noodles-during-handstand-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/brushing-teeth-while-whistling-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/smashing-boulder-on-chest-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blocking-pike-with-neck-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/swallowing-sword-to-stomach-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/catching-bullet-with-hands-eidolon_icon_small.webp"
],
"1211": [
"https://starrail.honeyhunterworld.com/img/eidolon/ambrosial-aqua-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sylphic-slumber-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/omniscient-opulence-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/evil-excision-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/waning-worries-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/drooling-drop-of-draconic-divinity-eidolon_icon_small.webp"
],
"1212": [
"https://starrail.honeyhunterworld.com/img/eidolon/moon-crashes-tianguan-gate-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/crescent-shadows-qixing-dipper-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/halfmoon-gapes-mercurial-haze-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lunarlance-shines-skyward-dome-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/night-shades-astral-radiance-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/eclipse-hollows-corporeal-husk-eidolon-2_icon_small.webp"
],
"1213": [
"https://starrail.honeyhunterworld.com/img/eidolon/tethered-to-sky-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/imperium-on-cloud-nine-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/clothed-in-clouds-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/zephyrs-bliss-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fall-is-the-pride-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/reign-returned-eidolon_icon_small.webp"
],
"1214": [
"https://starrail.honeyhunterworld.com/img/eidolon/dvesha-inhibited-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/klesha-breached-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dukha-ceased-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/karma-severed-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/deva-enthralled-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sasra-mastered-eidolon_icon_small.webp"
],
"1215": [
"https://starrail.honeyhunterworld.com/img/eidolon/one-heart-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/two-views-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/three-temptations-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/four-truths-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/five-skandhas-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/six-reverences-eidolon_icon_small.webp"
],
"1217": [
"https://starrail.honeyhunterworld.com/img/eidolon/anchored-to-vessel-specters-nestled-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sealed-in-tail-wraith-subdued-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cursed-by-fate-moths-to-flame-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/tied-in-life-bound-to-strife-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/mandated-by-edict-evils-evicted-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/woven-together-cohere-forever-eidolon_icon_small.webp"
],
"1218": [
"https://starrail.honeyhunterworld.com/img/eidolon/pentapathic-transference-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/from-savor-comes-suffer-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/flavored-euphony-reigns-supreme-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/leisure-in-luster-out-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/duel-in-dawn-dash-in-dusk-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/nonamorphic-pyrobind-eidolon_icon_small.webp"
],
"1220": [
"https://starrail.honeyhunterworld.com/img/eidolon/skyward-i-quell-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/moonward-i-wish-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/starward-i-bode-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/stormward-i-hear-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/heavenward-i-leap-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/homeward-i-near-eidolon_icon_small.webp"
],
"1221": [
"https://starrail.honeyhunterworld.com/img/eidolon/weathered-blade-does-not-sully-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/first-luster-breaks-dawn-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/mastlength-twirls-mountweight-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/artisans-ironsong-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blade-of-old-outlasts-all-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/walk-in-blade-talk-in-zither-eidolon_icon_small.webp"
],
"1222": [
"https://starrail.honeyhunterworld.com/img/eidolon/bloom-on-vileward-bouquet-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/leisure-in-carmine-smokeveil-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/shine-of-floral-wick-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/redolence-from-canopied-banquet-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/poise-atop-twists-and-turns-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/arcadia-under-deep-seclusion-eidolon_icon_small.webp"
],
"1223": [
"https://starrail.honeyhunterworld.com/img/eidolon/oathkeeper-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/wrathbearer-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/deathchaser-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/heathprowler-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/truthbender-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/faithbinder-eidolon_icon_small.webp"
],
"1224": [
"https://starrail.honeyhunterworld.com/img/eidolon/my-sword-stirs-starlight-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blade-dances-on-waves-fight-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sharp-wit-in-martial-might-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/being-fabulous-never-frights-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sword-delights-sugar-blights-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/me-the-best-girl-in-sight-eidolon_icon_small.webp"
],
"1225": [
"https://starrail.honeyhunterworld.com/img/eidolon/earthbound-i-was-cloudward-i-be-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/beatitude-dawns-for-the-worthy-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/verity-weaves-thoughts-to-blade-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bereft-of-form-which-name-to-bear-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/colored-cloud-rains-fortune-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/clairvoyance-of-boom-and-doom-eidolon_icon_small.webp"
],
"1301": [
"https://starrail.honeyhunterworld.com/img/eidolon/salty-dog-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lions-tail-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/corpse-reviver-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/last-word-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/death-in-the-afternoon-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blood-and-sand-eidolon_icon_small.webp"
],
"1302": [
"https://starrail.honeyhunterworld.com/img/eidolon/a-lacuna-in-kingdom-of-aesthetics-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/agates-humility-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/thorny-roads-glory-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/trumpets-dedication-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/snow-from-somewhere-in-cosmos-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/your-resplendence-eidolon_icon_small.webp"
],
"1303": [
"https://starrail.honeyhunterworld.com/img/eidolon/neuronic-embroidery-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/reedside-promenade-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/viridescent-pirouette-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/chatoyant-clat-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/languid-barrette-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sash-cascade-eidolon_icon_small.webp"
],
"1304": [
"https://starrail.honeyhunterworld.com/img/eidolon/prisoners-dilemma-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bounded-rationality-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/droprate-maxing-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/unexpected-hanging-paradox-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/ambiguity-aversion-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/stag-hunt-game-eidolon_icon_small.webp"
],
"1305": [
"https://starrail.honeyhunterworld.com/img/eidolon/pride-comes-before-a-fall-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-divine-is-in-the-details-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/know-thyself-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/ignorance-is-blight-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sic-itur-ad-astra-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/vincit-omnia-veritas-eidolon_icon_small.webp"
],
"1306": [
"https://starrail.honeyhunterworld.com/img/eidolon/suspension-of-disbelief-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/purely-fictitious-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pipedream-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/life-is-a-gamble-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/parallax-truth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/narrative-polysemy-eidolon_icon_small.webp"
],
"1307": [
"https://starrail.honeyhunterworld.com/img/eidolon/seven-pillars-of-wisdom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/weep-not-for-me-my-lamb-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/as-above-so-below-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/in-tears-we-gift-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/linnutee-flyway-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pantheon-merciful-masses-pitiful-eidolon_icon_small.webp"
],
"1308": [
"https://starrail.honeyhunterworld.com/img/eidolon/silenced-sky-spake-sooth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/mute-thunder-in-still-tempest-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/frost-bites-in-death-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/shrined-fire-for-mirrored-soul-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/strewn-souls-on-erased-earths-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/apocalypse-the-emancipator-eidolon_icon_small.webp"
],
"1309": [
"https://starrail.honeyhunterworld.com/img/eidolon/land-of-smiles-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/afternoon-tea-for-two-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/inverted-tuning-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/raindrop-key-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lonestars-lament-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/moonless-midnight-eidolon_icon_small.webp"
],
"1310": [
"https://starrail.honeyhunterworld.com/img/eidolon/in-reddened-chrysalis-i-once-rest-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/from-shattered-sky-i-free-fall-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/amidst-silenced-stars-i-deep-sleep-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/upon-lighted-fyrefly-i-soon-gaze-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/from-undreamt-night-i-thence-shine-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/in-finalized-morrow-i-full-bloom-eidolon_icon_small.webp"
],
"1312": [
"https://starrail.honeyhunterworld.com/img/eidolon/whimsicality-of-fancy-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/yearning-of-youth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/vestige-of-happiness-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/visage-of-kinship-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/genesis-of-first-love-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/estrangement-of-dream-eidolon_icon_small.webp"
],
"1313": [
"https://starrail.honeyhunterworld.com/img/eidolon/millenniums-quietus-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/faith-outstrips-frailty-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hermitage-of-thorns-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sculptures-preamble-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/paper-raft-in-silver-bay-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dawn-of-sidereal-cacophony-eidolon_icon_small.webp"
],
"1314": [
"https://starrail.honeyhunterworld.com/img/eidolon/altruism-nevertheless-tradable-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/morality-herein-authenticated-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/honesty-soon-mortgaged-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sincerity-put-option-only-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hope-hitherto-forfeited-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/equity-pending-sponsorship-eidolon_icon_small.webp"
],
"1315": [
"https://starrail.honeyhunterworld.com/img/eidolon/dusty-trails-lone-star-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/milestonemonger-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/marble-orchards-guard-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/cold-cuts-chef-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/stump-speech-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/crowbar-hotels-raccoon-eidolon_icon_small.webp"
],
"1317": [
"https://starrail.honeyhunterworld.com/img/eidolon/returned-is-the-revenant-with-no-ferry-toll-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/free-is-the-mind-enlightened-by-haikus-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/many-are-the-shrines-that-repel-no-hell-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lost-is-the-nind-devoured-by-time-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/steady-is-the-ranger-with-unerring-arrows-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/righteous-is-the-wrath-that-spares-no-evil-eidolon_icon_small.webp"
],
"1321": [
"https://starrail.honeyhunterworld.com/img/eidolon/when-a-bud-readies-to-bloom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fresh-ethereal-and-beloved-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pity-its-petals-thin-as-mist-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pity-its-heart-gnawed-by-worms-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fallen-decayed-and-despised-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/and-yet-always-deathly-beautiful-eidolon_icon_small.webp"
],
"1401": [
"https://starrail.honeyhunterworld.com/img/eidolon/night-at-shorefall-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/wind-through-keyhole-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/door-into-summer-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-sixteenth-key-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bitter-pill-of-truth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sweet-lure-of-answer-eidolon_icon_small.webp"
],
"1402": [
"https://starrail.honeyhunterworld.com/img/eidolon/drift-at-the-whim-of-venus-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sail-on-the-raft-of-eyelids-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bequeath-in-the-coalescence-of-dew-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/flicker-below-the-surface-of-marble-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/weave-under-the-shroud-of-woe-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fluctuate-in-the-tapestry-of-fates-eidolon_icon_small.webp"
],
"1403": [
"https://starrail.honeyhunterworld.com/img/eidolon/rite-of-sugar-scoop-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/guide-of-dream-tour-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/trove-of-morning-glow-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/peace-of-empathy-bond-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/clock-of-wonder-origin-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/morrow-of-star-shine-eidolon_icon_small.webp"
],
"1404": [
"https://starrail.honeyhunterworld.com/img/eidolon/frost-hones-spine-of-steel-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/strife-beholds-cry-of-dead-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/honor-exalts-feast-of-faith-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/siren-jolts-the-laconic-lion-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/war-chisels-flesh-of-flame-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/legacy-scales-mound-of-blood-eidolon_icon_small.webp"
],
"1405": [
"https://starrail.honeyhunterworld.com/img/eidolon/magician-isolated-by-stars-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/soul-true-to-history-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pupil-etched-into-cosmos-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blaze-plunged-to-canyon-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/embryo-set-beyond-vortex-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/everything-is-in-everything-eidolon_icon_small.webp"
],
"1406": [
"https://starrail.honeyhunterworld.com/img/eidolon/read-the-room-seek-the-glee-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/in-the-fray-nab-on-a-spree-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/from-thin-air-hard-to-foresee-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-jig-is-up-quick-to-flee-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/safe-in-numbers-light-as-a-bee-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-thiefs-game-unsung-and-free-eidolon_icon_small.webp"
],
"1407": [
"https://starrail.honeyhunterworld.com/img/eidolon/snowbound-maiden-memory-to-tomb-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/crown-on-wings-of-bloom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pious-pilgrim-dance-in-doom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/rest-in-songs-of-gloom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/pristine-pages-prophecy-as-plume-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/await-for-years-to-loom-eidolon_icon_small.webp"
],
"1408": [
"https://starrail.honeyhunterworld.com/img/eidolon/fire-and-light-bind-virtue-and-vice-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sky-and-earth-churn-mortal-froth-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/depths-of-quiet-entombed-in-ruin-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/faces-of-titans-blurred-by-time-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-wheel-spins-forevermore-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/embers-of-old-rise-still-eidolon_icon_small.webp"
],
"1409": [
"https://starrail.honeyhunterworld.com/img/eidolon/cradle-the-candle-of-night-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/come-sit-in-my-courtyard-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/depart-unto-the-sun-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sunlit-amber-yours-to-keep-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/twilight-drapes-the-tide-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/o-sky-heed-my-plea-eidolon_icon_small.webp"
],
"1410": [
"https://starrail.honeyhunterworld.com/img/eidolon/you-ask-why-hearts-cry-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/tell-me-why-waves-roar-high-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/why-do-lights-bid-goodbye-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lo-how-time-flows-by-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/in-ablution-i-hum-and-sigh-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/when-to-return-from-where-you-lie-eidolon_icon_small.webp"
],
"1412": [
"https://starrail.honeyhunterworld.com/img/eidolon/seize-the-crowns-of-all-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/forge-the-dreams-of-many-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/torch-the-laws-of-old-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/remake-the-realms-of-men-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/help-and-hurt-repaid-in-full-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-journey-set-starward-eidolon_icon_small.webp"
],
"1413": [
"https://starrail.honeyhunterworld.com/img/eidolon/sleep-tight-the-night-dreams-long-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/listen-up-the-slumber-speaks-soft-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/fear-not-the-nightmare-lies-past-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/wake-up-the-tomorrow-is-yours-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/let-go-the-me-in-memories-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/like-this-always-eidolon_icon_small.webp"
],
"1414": [
"https://starrail.honeyhunterworld.com/img/eidolon/shed-scales-of-old-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/watch-trails-to-blaze-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bear-weight-of-worlds-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/by-oath-this-vessel-is-i-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/the-path-of-permanence-sweeps-far-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/one-dream-to-enfold-all-wilds-eidolon_icon_small.webp"
],
"1415": [
"https://starrail.honeyhunterworld.com/img/eidolon/epics-born-on-a-blank-slate-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-tomorrow-in-thirteen-shades-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/by-thy-being-as-ive-written-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/please-write-on-with-a-smile-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/gaze-steeped-in-yesterbloom-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/remembrance-sung-in-ripples-%E2%99%AA-eidolon_icon_small.webp"
],
"1501": [
"https://starrail.honeyhunterworld.com/img/eidolon/goingviral-whoisshe-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/audienceknows-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/linkup-heartskip-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/lockedin-facecard-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/healingtheworld-goodvibesonly-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/builtdifferent-goingextinct-eidolon_icon_small.webp"
],
"1502": [
"https://starrail.honeyhunterworld.com/img/eidolon/chuckle-chimes-where-jade-falls-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/blind-arrows-guided-by-feathers-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/auspices-mirrored-in-decalight-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/threads-of-fate-colored-by-plumes-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bejeweled-in-radiant-grace-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/ferried-along-the-astral-arc-eidolon_icon_small.webp"
],
"8001": [
"https://starrail.honeyhunterworld.com/img/eidolon/a-falling-star-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/an-unwilling-host-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-leading-whisper-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-destructing-glance-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-surviving-hope-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-trailblazing-will-eidolon_icon_small.webp"
],
"8002": [
"https://starrail.honeyhunterworld.com/img/eidolon/a-falling-star-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/an-unwilling-host-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-leading-whisper-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-destructing-glance-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-surviving-hope-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/a-trailblazing-will-eidolon-2_icon_small.webp"
],
"8003": [
"https://starrail.honeyhunterworld.com/img/eidolon/earth-shaking-resonance-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/time-defying-tenacity-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/trail-blazing-blueprint-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/nation-building-oath-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/spirit-warming-flame-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/city-forging-bulwarks-eidolon_icon_small.webp"
],
"8004": [
"https://starrail.honeyhunterworld.com/img/eidolon/earth-shaking-resonance-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/time-defying-tenacity-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/trail-blazing-blueprint-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/nation-building-oath-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/spirit-warming-flame-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/city-forging-bulwarks-eidolon-2_icon_small.webp"
],
"8005": [
"https://starrail.honeyhunterworld.com/img/eidolon/best-seat-in-the-house-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/jailbreaking-rainbowwalk-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sanatorium-for-rest-notes-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dove-in-tophat-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/poem-favors-rhythms-of-old-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/tomorrow-rest-in-spotlight-eidolon_icon_small.webp"
],
"8006": [
"https://starrail.honeyhunterworld.com/img/eidolon/best-seat-in-the-house-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/jailbreaking-rainbowwalk-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/sanatorium-for-rest-notes-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dove-in-tophat-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/poem-favors-rhythms-of-old-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/tomorrow-rest-in-spotlight-eidolon-2_icon_small.webp"
],
"8007": [
"https://starrail.honeyhunterworld.com/img/eidolon/narrator-of-the-present-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/gleaner-of-the-past-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/chanter-of-the-future-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dancer-of-the-muse-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/seamster-of-the-ode-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bearer-of-the-revelation-eidolon_icon_small.webp"
],
"8008": [
"https://starrail.honeyhunterworld.com/img/eidolon/narrator-of-the-present-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/gleaner-of-the-past-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/chanter-of-the-future-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/dancer-of-the-muse-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/seamster-of-the-ode-eidolon-2_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/bearer-of-the-revelation-eidolon-2_icon_small.webp"
],
"1504": [
"https://starrail.honeyhunterworld.com/img/eidolon/beware-wander-not-in-full-moon-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/knock-where-snickers-echo-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/hush-weight-of-unspoken-truths-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/heed-truth-needs-no-mastication-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/advisory-the-sleuth-is-but-the-slayer-eidolon_icon_small.webp",
"https://starrail.honeyhunterworld.com/img/eidolon/finale-and-then-there-were-none-eidolon_icon_small.webp"
]
}

View File

@@ -1,479 +0,0 @@
{
"101": {
"2": [
{
"type": "HealRatioBase",
"value": 0.10000000149011612
}
],
"4": []
},
"102": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
],
"4": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
]
},
"103": {
"2": [
{
"type": "DefenceAddedRatio",
"value": 0.15000000596046448
}
],
"4": []
},
"104": {
"2": [
{
"type": "IceAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"105": {
"2": [
{
"type": "PhysicalAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"106": {
"2": [],
"4": []
},
"107": {
"2": [
{
"type": "FireAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"108": {
"2": [
{
"type": "QuantumAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"109": {
"2": [
{
"type": "ThunderAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"110": {
"2": [
{
"type": "WindAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"111": {
"2": [
{
"type": "BreakDamageAddedRatioBase",
"value": 0.1599999964237213
}
],
"4": [
{
"type": "BreakDamageAddedRatioBase",
"value": 0.1599999964237213
}
]
},
"112": {
"2": [
{
"type": "ImaginaryAddedRatio",
"value": 0.10000000149011612
}
],
"4": []
},
"113": {
"2": [
{
"type": "HPAddedRatio",
"value": 0.119999997317791
}
],
"4": []
},
"114": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
],
"4": []
},
"115": {
"2": [],
"4": []
},
"116": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
],
"4": []
},
"117": {
"2": [],
"4": [
{
"type": "CriticalChanceBase",
"value": 0.03999999910593033
}
]
},
"118": {
"2": [
{
"type": "BreakDamageAddedRatioBase",
"value": 0.1599999964237213
}
],
"4": []
},
"119": {
"2": [
{
"type": "BreakDamageAddedRatioBase",
"value": 0.1599999964237213
}
],
"4": []
},
"120": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
],
"4": [
{
"type": "CriticalChanceBase",
"value": 0.05999999865889549
}
]
},
"121": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
],
"4": []
},
"122": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.07999999821186066
}
],
"4": []
},
"123": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
],
"4": []
},
"124": {
"2": [
{
"type": "QuantumAddedRatio",
"value": 0.10000000149011612
}
],
"4": [
{
"type": "SpeedAddedRatio",
"value": -0.07999999821186066
}
]
},
"125": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
],
"4": []
},
"126": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
],
"4": []
},
"127": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.0800000000745058
}
],
"4": []
},
"128": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
],
"4": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
]
},
"301": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
]
},
"302": {
"2": [
{
"type": "HPAddedRatio",
"value": 0.119999997317791
}
]
},
"303": {
"2": [
{
"type": "StatusProbabilityBase",
"value": 0.10000000149011612
}
]
},
"304": {
"2": [
{
"type": "DefenceAddedRatio",
"value": 0.15000000596046448
}
]
},
"305": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
]
},
"306": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.07999999821186066
}
]
},
"307": {
"2": [
{
"type": "BreakDamageAddedRatioBase",
"value": 0.1599999964237213
}
]
},
"308": {
"2": [
{
"type": "SPRatioBase",
"value": 0.05000000074505806
}
]
},
"309": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.07999999821186066
}
]
},
"310": {
"2": [
{
"type": "StatusResistanceBase",
"value": 0.10000000149011612
}
]
},
"311": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
]
},
"312": {
"2": [
{
"type": "SPRatioBase",
"value": 0.05000000074505806
}
]
},
"313": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.03999999910593033
}
]
},
"314": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
]
},
"315": {
"2": []
},
"316": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
]
},
"317": {
"2": [
{
"type": "SPRatioBase",
"value": 0.05000000074505806
}
]
},
"318": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
]
},
"319": {
"2": [
{
"type": "HPAddedRatio",
"value": 0.119999997317791
}
]
},
"320": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
]
},
"321": {
"2": []
},
"322": {
"2": [
{
"type": "AttackAddedRatio",
"value": 0.119999997317791
}
]
},
"323": {
"2": [
{
"type": "CriticalChanceBase",
"value": 0.07999999821186066
}
]
},
"324": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
]
},
"130": {
"2": [
{
"type": "SpeedAddedRatio",
"value": 0.05999999865889549
}
]
},
"129": {
"2": [
{
"type": "CriticalDamageBase",
"value": 0.1599999964237213
}
]
},
"325": {
"2": [
{
"type": "ElationDamageAddedRatioBase",
"value": 0.1
}
]
},
"326": {
"2": [
]
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,298 +0,0 @@
{
"2": {
"1": {
"property": "HPDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 6.774007797241211,
"step": 0.8467509746551514,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 6.774007797241211,
"step": 0.8467509746551514,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.01728000119328499,
"step": 0.002160000381991267,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.0,
"step": 0.10000000149011612,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.010368000715970991,
"step": 0.001296000787988305,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.01382400095462799,
"step": 0.001728000584989786,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
}
},
"3": {
"1": {
"property": "HPDelta",
"base": 20.322023391723633,
"step": 2.540252923965454,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 10.161011695861816,
"step": 1.2701259851455688,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 10.161011695861816,
"step": 1.2701259851455688,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.025919999927282333,
"step": 0.0032400002237409353,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.2000000476837158,
"step": 0.10000000149011612,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.015552000142633917,
"step": 0.001944000832736492,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.03110400028526783,
"step": 0.003888000966981054,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.03110400028526783,
"step": 0.003888000966981054,
"step_num": 2
}
},
"4": {
"1": {
"property": "HPDelta",
"base": 27.096031188964844,
"step": 3.3870038986206055,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 13.548015594482422,
"step": 1.6935019493103027,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 1.600000023841858,
"step": 0.20000000298023224,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.020736001431941983,
"step": 0.0025920008774846792,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.041471999138593674,
"step": 0.0051840003579854965,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.027648000046610832,
"step": 0.0034560004714876413,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.041471999138593674,
"step": 0.0051840003579854965,
"step_num": 2
}
},
"5": {
"1": {
"property": "HPDelta",
"base": 33.87004089355469,
"step": 4.233755111694336,
"step_num": 2
},
"2": {
"property": "AttackDelta",
"base": 16.93501853942871,
"step": 2.1168770790100098,
"step_num": 2
},
"3": {
"property": "DefenceDelta",
"base": 16.93501853942871,
"step": 2.1168770790100098,
"step_num": 2
},
"4": {
"property": "HPAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"5": {
"property": "AttackAddedRatio",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"6": {
"property": "DefenceAddedRatio",
"base": 0.04320000112056732,
"step": 0.005400000140070915,
"step_num": 2
},
"7": {
"property": "SpeedDelta",
"base": 2.0,
"step": 0.30000001192092896,
"step_num": 2
},
"8": {
"property": "CriticalChanceBase",
"base": 0.025919999927282333,
"step": 0.0032400002237409353,
"step_num": 2
},
"9": {
"property": "CriticalDamageBase",
"base": 0.05183999985456467,
"step": 0.006480000447481871,
"step_num": 2
},
"10": {
"property": "StatusProbabilityBase",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"11": {
"property": "StatusResistanceBase",
"base": 0.03456000238656998,
"step": 0.004320000298321247,
"step_num": 2
},
"12": {
"property": "BreakDamageAddedRatioBase",
"base": 0.05183999985456467,
"step": 0.006480000447481871,
"step_num": 2
}
}
}

View File

@@ -0,0 +1,25 @@
import { getDataCache } from "@/lib/cache/cache"
export async function GET(
req: Request,
{ params }: { params: Promise<{ name: string }> }
) {
const { name } = await params
const item = getDataCache(name)
if (!item) {
return new Response("Not found", { status: 404 })
}
const headers: Record<string, string> = {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=3600"
}
if (item.type === "br") {
headers["Content-Encoding"] = "br"
}
return new Response(new Uint8Array(item.buf), { headers })
}

View File

@@ -73,22 +73,22 @@ export default async function RootLayout({
<QueryProviderWrapper> <QueryProviderWrapper>
<ThemeProvider> <ThemeProvider>
<ClientThemeWrapper> <ClientThemeWrapper>
<ClientDataFetcher /> <ClientDataFetcher >
<div className="min-h-screen w-full"> <div className="min-h-screen w-full">
<Header /> <Header />
<div className="grid grid-cols-12 w-full"> <div className="grid grid-cols-12 w-full">
<div className="hidden sm:block md:col-span-4 lg:col-span-3 sticky top-0 self-start h-fit"> <div className="hidden sm:block md:col-span-4 lg:col-span-3 sticky top-0 self-start h-fit">
<AvatarBar /> <AvatarBar />
</div> </div>
<div className="col-span-12 sm:col-span-8 lg:col-span-9"> <div className="col-span-12 sm:col-span-8 lg:col-span-9">
<ActionBar /> <ActionBar />
{children} {children}
</div>
</div> </div>
<Footer />
</div> </div>
</ClientDataFetcher>
<Footer />
</div>
</ClientThemeWrapper> </ClientThemeWrapper>
</ThemeProvider> </ThemeProvider>
</QueryProviderWrapper> </QueryProviderWrapper>

View File

@@ -1,7 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client"; "use client";
import useListAvatarStore from "@/stores/avatarStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
@@ -19,12 +18,14 @@ import { connectToPS, syncDataToPS } from "@/helper";
import CopyImport from "../importBar/copy"; import CopyImport from "../importBar/copy";
import useCopyProfileStore from "@/stores/copyProfile"; import useCopyProfileStore from "@/stores/copyProfile";
import AvatarBar from "../avatarBar"; import AvatarBar from "../avatarBar";
import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore";
export default function ActionBar() { export default function ActionBar() {
const router = useRouter() const router = useRouter()
const { avatarSelected, listRawAvatar } = useListAvatarStore() const { avatarSelected } = useCurrentDataStore()
const { setListCopyAvatar } = useCopyProfileStore() const { damageType, baseType } = useDetailDataStore()
const { setResetData } = useCopyProfileStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
@@ -43,21 +44,20 @@ export default function ActionBar() {
const profileCurrent = useMemo(() => { const profileCurrent = useMemo(() => {
if (!avatarSelected) return null; if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id]; const avatar = avatars[avatarSelected.ID];
return avatar?.profileList[avatar.profileSelect] || null; return avatar?.profileList[avatar.profileSelect] || null;
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
const listProfile = useMemo(() => { const listProfile = useMemo(() => {
if (!avatarSelected) return []; if (!avatarSelected) return [];
const avatar = avatars[avatarSelected.id]; const avatar = avatars[avatarSelected.ID];
return avatar?.profileList || []; return avatar?.profileList || [];
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
const handleUpdateProfile = () => { const handleUpdateProfile = () => {
if (!profileName.trim()) return; if (!profileName.trim()) return;
if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.id]) { if (formState === "CREATE" && avatarSelected && avatars[avatarSelected.ID]) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
const newProfile = { const newProfile = {
profile_name: profileName, profile_name: profileName,
@@ -65,12 +65,12 @@ export default function ActionBar() {
relics: {} as Record<string, RelicStore> relics: {} as Record<string, RelicStore>
} }
newListProfile.push(newProfile) newListProfile.push(newProfile)
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: newListProfile.length - 1 }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: newListProfile.length - 1 })
toast.success("Profile created successfully") toast.success("Profile created successfully")
} else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) { } else if (formState === "EDIT" && profileCurrent && avatarSelected && profileEdit !== -1) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
newListProfile[profileEdit].profile_name = profileName; newListProfile[profileEdit].profile_name = profileName;
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile })
toast.success("Profile updated successfully") toast.success("Profile updated successfully")
} }
handleCloseModal("update_profile_modal"); handleCloseModal("update_profile_modal");
@@ -99,20 +99,19 @@ export default function ActionBar() {
const handleProfileSelect = (profileId: number) => { const handleProfileSelect = (profileId: number) => {
if (!avatarSelected) return; if (!avatarSelected) return;
if (avatars[avatarSelected.id].profileSelect === profileId) return; if (avatars[avatarSelected.ID].profileSelect === profileId) return;
setAvatar({ ...avatars[avatarSelected.id], profileSelect: profileId }) setAvatar({ ...avatars[avatarSelected.ID], profileSelect: profileId })
toast.success(`Profile changed to Profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}`) toast.success(`Profile changed to Profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}`)
} }
const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => { const handleDeleteProfile = (profileId: number, e: React.MouseEvent) => {
e.stopPropagation() e.stopPropagation()
if (!avatarSelected || profileId == 0) return; if (!avatarSelected || profileId == 0) return;
if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.id].profileList[profileId].profile_name}?`)) { if (window.confirm(`Are you sure you want to delete profile: ${avatars[avatarSelected.ID].profileList[profileId].profile_name}?`)) {
const newListProfile = [...listProfile] const newListProfile = [...listProfile]
newListProfile.splice(profileId, 1) newListProfile.splice(profileId, 1)
setAvatar({ ...avatars[avatarSelected.id], profileList: newListProfile, profileSelect: profileId - 1 }) setAvatar({ ...avatars[avatarSelected.ID], profileList: newListProfile, profileSelect: profileId - 1 })
toast.success(`Profile ${avatars[avatarSelected.id].profileList[profileId].profile_name} deleted successfully`) toast.success(`Profile ${avatars[avatarSelected.ID].profileList[profileId].profile_name} deleted successfully`)
} }
} }
@@ -219,28 +218,26 @@ export default function ActionBar() {
<div className="flex flex-wrap items-center gap-2 "> <div className="flex flex-wrap items-center gap-2 ">
<div className="flex flex-wrap items-center h-full opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl"> <div className="flex flex-wrap items-center h-full opacity-80 lg:hover:opacity-100 cursor-pointer text-base md:text-lg lg:text-xl">
{avatarSelected && ( {avatarSelected && (
<div className="flex items-center justify-start h-full w-full"> <div className="flex flex-wrap items-center justify-start h-full w-full">
<Image <Image
src={`/icon/${avatarSelected.damageType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${damageType?.[avatarSelected?.DamageType]?.Icon}`}
alt={'fire'} alt={'damage type'}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
className="h-10 w-10 object-contain" className="h-10 w-10 object-contain"
width={100} width={100}
height={100} height={100}
/> />
<p className="text-center font-bold text-xl"> <p className="text-center font-bold text-lg">
{transI18n(avatarSelected.baseType.toLowerCase())} {transI18n(avatarSelected.BaseType.toLowerCase())}
</p> </p>
<div className="text-center font-bold text-xl">{" / "}</div> <div className="text-center font-bold text-lg">{" / "}</div>
<ParseText <ParseText
locale={locale} locale={locale}
text={getNameChar(locale, transI18n, avatarSelected).toWellFormed()} text={getNameChar(locale, transI18n, avatarSelected).toWellFormed()}
className={"font-bold text-xl"} className={"font-bold text-lg"}
/> />
{avatarSelected?.id && ( <div className="text-center italic text-sm ml-2"> {`(${avatarSelected.ID})`}</div>
<div className="text-center italic text-sm ml-2"> {`(${avatarSelected.id})`}</div>
)}
</div> </div>
)} )}
</div> </div>
@@ -315,7 +312,7 @@ export default function ActionBar() {
<button <button
onClick={() => { onClick={() => {
setIsOpenCopy(true) setIsOpenCopy(true)
setListCopyAvatar(listRawAvatar) setResetData(baseType, damageType)
handleShow("copy_profile_modal") handleShow("copy_profile_modal")
}} }}
className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20" className="btn btn-ghost flex justify-start px-3 py-2 h-full w-full hover:bg-base-200 cursor-pointer text-primary z-20"

View File

@@ -1,32 +1,57 @@
"use client" "use client"
import Image from "next/image" import Image from "next/image"
import { useEffect } from "react" import { useMemo } from "react"
import CharacterCard from "../card/characterCard" import CharacterCard from "../card/characterCard"
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import useAvatarStore from "@/stores/avatarStore"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import useDetailDataStore from "@/stores/detailDataStore"
import useCurrentDataStore from '@/stores/currentDataStore';
import { calcRarity, getNameChar } from "@/helper"
export default function AvatarBar({ onClose }: { onClose?: () => void }) { export default function AvatarBar({ onClose }: { onClose?: () => void }) {
const { const {
listAvatar, avatarSearch,
mapAvatarElementActive,
mapAvatarPathActive,
setAvatarSearch,
setAvatarSelected, setAvatarSelected,
setSkillSelected, setMapAvatarElementActive,
setFilter, setMapAvatarPathActive,
filter, setSkillIDSelected,
listElement, } = useCurrentDataStore()
listPath, const { mapAvatar, baseType, damageType } = useDetailDataStore()
setListElement,
setListPath
} = useAvatarStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore() const {locale} = useLocaleStore()
const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return []
useEffect(() => { let list = Object.values(mapAvatar)
setFilter({ ...filter, locale: locale, element: Object.keys(listElement).filter((key) => listElement[key]), path: Object.keys(listPath).filter((key) => listPath[key]) })
// eslint-disable-next-line react-hooks/exhaustive-deps if (avatarSearch) {
}, [locale, listElement, listPath]) list = list.filter(item =>
getNameChar(locale, transI18n, item)
.toLowerCase()
.includes(avatarSearch.toLowerCase())
)
}
const allElementFalse = !Object.values(mapAvatarElementActive).some(v => v)
const allPathFalse = !Object.values(mapAvatarPathActive).some(v => v)
list = list.filter(item =>
(allElementFalse || mapAvatarElementActive[item.DamageType]) &&
(allPathFalse || mapAvatarPathActive[item.BaseType])
)
list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r
return a.ID - b.ID
})
return list
}, [mapAvatar, mapAvatarElementActive, mapAvatarPathActive, avatarSearch, locale, transI18n])
return ( return (
@@ -39,23 +64,23 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
<input type="text" <input type="text"
placeholder={transI18n("placeholderCharacter")} placeholder={transI18n("placeholderCharacter")}
className="input input-bordered input-primary w-full" className="input input-bordered input-primary w-full"
value={filter.name} value={avatarSearch}
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })} onChange={(e) => setAvatarSearch(e.target.value)}
/> />
</div> </div>
<div className="grid grid-cols-7 sm:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto"> <div className="grid grid-cols-7 sm:grid-cols-4 lg:grid-cols-7 mb-1 mx-1 gap-2 w-full max-h-[17vh] min-h-[5vh] overflow-y-auto">
{Object.keys(listElement).map((key, index) => ( {Object.entries(damageType).map(([key, value]) => (
<div <div
key={index} key={key}
onClick={() => { onClick={() => {
setListElement({ ...listElement, [key]: !listElement[key] }) setMapAvatarElementActive({ ...mapAvatarElementActive, [key]: !mapAvatarElementActive[key] })
}} }}
className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg" className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg"
style={{ style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280" backgroundColor: mapAvatarElementActive[key] ? "#374151" : "#6B7280"
}}> }}>
<Image <Image
src={`/icon/${key}.webp`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
@@ -67,20 +92,20 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
</div> </div>
<div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]"> <div className="grid grid-cols-9 sm:grid-cols-5 lg:grid-cols-9 mb-1 mx-1 gap-2 overflow-y-auto w-full max-h-[17vh] min-h-[5vh]">
{Object.keys(listPath).map((key, index) => ( {Object.entries(baseType).map(([key, value]) => (
<div <div
key={index} key={key}
onClick={() => { onClick={() => {
setListPath({ ...listPath, [key]: !listPath[key] }) setMapAvatarPathActive({ ...mapAvatarPathActive, [key]: !mapAvatarPathActive[key] })
}} }}
className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer" className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280" backgroundColor: mapAvatarPathActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<Image <Image
src={`/icon/${key}.webp`} src={`${process.env.CDN_URL}/${value.Icon}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt={key} alt={key}
@@ -98,7 +123,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
{listAvatar.map((item, index) => ( {listAvatar.map((item, index) => (
<div key={index} onClick={() => { <div key={index} onClick={() => {
setAvatarSelected(item); setAvatarSelected(item);
setSkillSelected(null) setSkillIDSelected(null)
if (onClose) onClose() if (onClose) onClose()
}}> }}>
<CharacterCard data={item} /> <CharacterCard data={item} />

View File

@@ -1,41 +1,38 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client" "use client"
import useAvatarStore from "@/stores/avatarStore"
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import LightconeBar from '../lightconeBar' import LightconeBar from '../lightconeBar'
import useLightconeStore from '@/stores/lightconeStore'
import { calcPromotion, calcRarity, replaceByParam } from '@/helper'; import { calcPromotion, calcRarity, replaceByParam } from '@/helper';
import { getSkillTree } from '@/helper/getSkillTree'; import { getSkillTree } from '@/helper/getSkillTree';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import ParseText from '../parseText'; import ParseText from '../parseText';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import useModelStore from '@/stores/modelStore'; import useModelStore from '@/stores/modelStore';
import useMazeStore from '@/stores/mazeStore';
import Image from 'next/image'; import Image from 'next/image';
import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { getLocaleName } from "@/helper/getName";
export default function AvatarInfo() { export default function AvatarInfo() {
const { avatarSelected, mapAvatarInfo } = useAvatarStore() const { avatarSelected, setResetDataLightcone } = useCurrentDataStore()
const { Technique } = useMazeStore()
const { avatars, setAvatars, setAvatar } = useUserDataStore() const { avatars, setAvatars, setAvatar } = useUserDataStore()
const { isOpenLightcone, setIsOpenLightcone } = useModelStore() const { isOpenLightcone, setIsOpenLightcone } = useModelStore()
const { listLightcone, mapLightconeInfo, setDefaultFilter } = useLightconeStore() const { mapLightCone, baseType } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
const lightcone = useMemo(() => { const lightcone = useMemo(() => {
if (!avatarSelected) return null; if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id]; const avatar = avatars[avatarSelected.ID];
return avatar?.profileList[avatar.profileSelect]?.lightcone || null; return avatar?.profileList[avatar.profileSelect]?.lightcone || null;
}, [avatarSelected, avatars]); }, [avatarSelected, avatars]);
const lightconeDetail = useMemo(() => { const lightconeDetail = useMemo(() => {
if (!lightcone) return null; if (!mapLightCone || !lightcone?.item_id) return null;
return listLightcone.find((item) => Number(item.id) === Number(lightcone.item_id)) || null; return mapLightCone?.[lightcone.item_id.toString()] || null;
}, [lightcone, listLightcone]); }, [lightcone, mapLightCone]);
const handleShow = (modalId: string) => { const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
@@ -74,7 +71,7 @@ export default function AvatarInfo() {
return ( return (
<div className="bg-base-100 max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden"> <div className="bg-base-100 max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
{avatarSelected && avatars[avatarSelected?.id || ""] && ( {avatarSelected && avatars[avatarSelected?.ID.toString() || ""] && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
<div className="m-2 min-h-96"> <div className="m-2 min-h-96">
<div className="container"> <div className="container">
@@ -99,25 +96,25 @@ export default function AvatarInfo() {
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
<span className="label-text font-medium">{transI18n("characterLevel")}</span> <span className="label-text font-medium">{transI18n("characterLevel")}</span>
<span className="label-text-alt text-info font-mono">{avatars[avatarSelected?.id || ""]?.level}/80</span> <span className="label-text-alt text-info font-mono">{avatars[avatarSelected?.ID.toString() || ""]?.level}/80</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
type="number" type="number"
min="1" min="1"
max="80" max="80"
value={avatars[avatarSelected?.id || ""]?.level} value={avatars[avatarSelected?.ID.toString() || ""]?.level}
onChange={(e) => { onChange={(e) => {
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1)); const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } }); setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
}} }}
className="input input-bordered w-full pr-16 font-mono" className="input input-bordered w-full pr-16 font-mono"
placeholder={transI18n("placeholderLevel")} placeholder={transI18n("placeholderLevel")}
/> />
<div <div
onClick={() => { onClick={() => {
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: 80, promotion: calcPromotion(80) } }); setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: 80, promotion: calcPromotion(80) } });
}} }}
className="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/60 cursor-pointer"> className="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/60 cursor-pointer">
<span className="text-sm">{transI18n("max")}</span> <span className="text-sm">{transI18n("max")}</span>
@@ -128,10 +125,10 @@ export default function AvatarInfo() {
type="range" type="range"
min="1" min="1"
max="80" max="80"
value={avatars[avatarSelected?.id || ""]?.level} value={avatars[avatarSelected?.ID.toString() || ""]?.level}
onChange={(e) => { onChange={(e) => {
const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1)); const newLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], level: newLevel, promotion: calcPromotion(newLevel) } }); setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], level: newLevel, promotion: calcPromotion(newLevel) } });
}} }}
className="range range-info range-sm w-full" className="range range-info range-sm w-full"
/> />
@@ -150,33 +147,33 @@ export default function AvatarInfo() {
<label className="label"> <label className="label">
<span className="label-text font-medium">{transI18n("currentEnergy")}</span> <span className="label-text font-medium">{transI18n("currentEnergy")}</span>
<span className="label-text text-warning font-mono"> <span className="label-text text-warning font-mono">
{Math.round(avatars[avatarSelected?.id || ""]?.sp_value)}/{avatars[avatarSelected?.id || ""]?.sp_max} {Math.round(avatars[avatarSelected?.ID.toString() || ""]?.sp_value)}/{avatars[avatarSelected?.ID.toString() || ""]?.sp_max}
</span> </span>
</label> </label>
<input <input
type="range" type="range"
min="0" min="0"
max={avatars[avatarSelected?.id || ""]?.sp_max} max={avatars[avatarSelected?.ID.toString() || ""]?.sp_max}
value={avatars[avatarSelected?.id || ""]?.sp_value} value={avatars[avatarSelected?.ID.toString() || ""]?.sp_value}
onChange={(e) => { onChange={(e) => {
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return if (!avatars[avatarSelected?.ID.toString() || ""]?.can_change_sp) return
const newSpValue = Math.min(avatars[avatarSelected?.id || ""]?.sp_max, Math.max(0, parseInt(e.target.value) || 0)); const newSpValue = Math.min(avatars[avatarSelected?.ID.toString() || ""]?.sp_max, Math.max(0, parseInt(e.target.value) || 0));
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue } }); setAvatars({ ...avatars, [avatarSelected?.ID.toString() || ""]: { ...avatars[avatarSelected?.ID.toString() || ""], sp_value: newSpValue } });
}} }}
className="range range-warning range-sm w-full" className="range range-warning range-sm w-full"
/> />
<div className="flex justify-between text-sm text-base-content/60 mt-1"> <div className="flex justify-between text-sm text-base-content/60 mt-1">
<span>0%</span> <span>0%</span>
<span className="font-mono text-warning">{((avatars[avatarSelected?.id || ""]?.sp_value / avatars[avatarSelected?.id || ""]?.sp_max) * 100).toFixed(1)}%</span> <span className="font-mono text-warning">{((avatars[avatarSelected?.ID.toString() || ""]?.sp_value / avatars[avatarSelected?.ID.toString() || ""]?.sp_max) * 100).toFixed(1)}%</span>
<span>100%</span> <span>100%</span>
</div> </div>
<div className="mt-3"> <div className="mt-3">
<button <button
className="btn btn-sm btn-outline btn-warning" className="btn btn-sm btn-outline btn-warning"
onClick={() => { onClick={() => {
if (!avatars[avatarSelected?.id || ""]?.can_change_sp) return if (!avatars[avatarSelected?.ID.toString() || ""]?.can_change_sp) return
const newSpValue = Math.ceil(avatars[avatarSelected?.id || ""]?.sp_max / 2); const newSpValue = Math.ceil(avatars[avatarSelected?.ID.toString() || ""]?.sp_max / 2);
const newAvatar = { ...avatars[avatarSelected?.id || ""], sp_value: newSpValue } const newAvatar = { ...avatars[avatarSelected?.ID.toString() || ""], sp_value: newSpValue }
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
> >
@@ -203,11 +200,11 @@ export default function AvatarInfo() {
</div> </div>
<input <input
type="checkbox" type="checkbox"
checked={avatars[avatarSelected?.id || ""]?.techniques.length > 0} checked={(!avatarSelected || avatarSelected?.MazeBuff?.length > 0)}
onChange={(e) => { onChange={(e) => {
if (!Technique[avatarSelected?.id || ""] || Technique[avatarSelected?.id || ""]?.maze_buff.length === 0) return if (!avatarSelected || avatarSelected?.MazeBuff?.length > 0) return
const techniques = e.target.checked ? Technique[avatarSelected?.id || ""]?.maze_buff : []; const techniques = e.target.checked ? avatarSelected?.MazeBuff : [];
const newAvatar = { ...avatars[avatarSelected?.id || ""], techniques }; const newAvatar = { ...avatars[avatarSelected?.ID.toString() || ""], techniques: techniques };
setAvatar(newAvatar); setAvatar(newAvatar);
}} }}
className="toggle toggle-accent" className="toggle toggle-accent"
@@ -220,7 +217,7 @@ export default function AvatarInfo() {
</div> </div>
{/* Enhancement Selection */} {/* Enhancement Selection */}
{Object.entries(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).length > 0 && ( {avatarSelected?.Enhanced && (
<div className="bg-base-100 rounded-xl p-6 border border-base-content/10"> <div className="bg-base-100 rounded-xl p-6 border border-base-content/10">
<h4 className="text-lg font-semibold mb-4 flex items-center gap-2"> <h4 className="text-lg font-semibold mb-4 flex items-center gap-2">
<svg className="w-5 h-5 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -233,16 +230,16 @@ export default function AvatarInfo() {
<label className="label"> <label className="label">
<span className="label-text font-medium">{transI18n("enhancementLevel")}</span> <span className="label-text font-medium">{transI18n("enhancementLevel")}</span>
<span className="label-text-alt text-secondary font-mono"> <span className="label-text-alt text-secondary font-mono">
{avatars[avatarSelected?.id || ""]?.enhanced || transI18n("origin")} {avatars[avatarSelected?.ID.toString() || ""]?.enhanced || transI18n("origin")}
</span> </span>
</label> </label>
<select <select
value={avatars[avatarSelected?.id || ""]?.enhanced || ""} value={avatars[avatarSelected?.ID.toString() || ""]?.enhanced || ""}
onChange={(e) => { onChange={(e) => {
const newAvatar = avatars[avatarSelected?.id || ""] const newAvatar = avatars[avatarSelected?.ID.toString() || ""]
if (newAvatar) { if (newAvatar) {
newAvatar.enhanced = e.target.value newAvatar.enhanced = e.target.value
const skillTree = getSkillTree(e.target.value) const skillTree = getSkillTree(avatarSelected, e.target.value)
if (skillTree) { if (skillTree) {
newAvatar.data.skills = skillTree newAvatar.data.skills = skillTree
} }
@@ -252,7 +249,7 @@ export default function AvatarInfo() {
className="select select-bordered select-secondary" className="select select-bordered select-secondary"
> >
<option value="">{transI18n("origin")}</option> <option value="">{transI18n("origin")}</option>
{Object.keys(mapAvatarInfo[avatarSelected?.id || ""]?.Enhanced || {}).map((key) => ( {Object.keys(avatarSelected?.Enhanced || {}).map((key) => (
<option key={key} value={key}> <option key={key} value={key}>
{key} {key}
</option> </option>
@@ -308,7 +305,7 @@ export default function AvatarInfo() {
onChange={(e) => { onChange={(e) => {
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1)) const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) } const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
const newAvatar = { ...avatars[avatarSelected.id] } const newAvatar = { ...avatars[avatarSelected.ID] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
@@ -318,7 +315,7 @@ export default function AvatarInfo() {
<div <div
onClick={() => { onClick={() => {
const newLightcone = { ...lightcone, level: 80, promotion: calcPromotion(80) } const newLightcone = { ...lightcone, level: 80, promotion: calcPromotion(80) }
const newAvatar = { ...avatars[avatarSelected.id] } const newAvatar = { ...avatars[avatarSelected.ID] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
@@ -335,7 +332,7 @@ export default function AvatarInfo() {
onChange={(e) => { onChange={(e) => {
const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1)) const newLightconeLevel = Math.min(80, Math.max(1, parseInt(e.target.value) || 1))
const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) } const newLightcone = { ...lightcone, level: newLightconeLevel, promotion: calcPromotion(newLightconeLevel) }
const newAvatar = { ...avatars[avatarSelected.id] } const newAvatar = { ...avatars[avatarSelected.ID] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
@@ -360,7 +357,7 @@ export default function AvatarInfo() {
key={r} key={r}
onClick={() => { onClick={() => {
const newLightcone = { ...lightcone, rank: r } const newLightcone = { ...lightcone, rank: r }
const newAvatar = { ...avatars[avatarSelected.id] } const newAvatar = { ...avatars[avatarSelected.ID] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone newAvatar.profileList[newAvatar.profileSelect].lightcone = newLightcone
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
@@ -391,8 +388,7 @@ export default function AvatarInfo() {
<button <button
onClick={() => { onClick={() => {
if (avatarSelected) { if (avatarSelected) {
const newDefaultFilter = { path: [avatarSelected.baseType.toLowerCase()], rarity: [] } setResetDataLightcone(avatarSelected, baseType)
setDefaultFilter(newDefaultFilter)
handleShow("action_detail_modal") handleShow("action_detail_modal")
} }
}} }}
@@ -405,7 +401,7 @@ export default function AvatarInfo() {
</button> </button>
<button <button
onClick={() => { onClick={() => {
const newAvatar = { ...avatars[avatarSelected.id] } const newAvatar = { ...avatars[avatarSelected.ID] }
newAvatar.profileList[newAvatar.profileSelect].lightcone = null newAvatar.profileList[newAvatar.profileSelect].lightcone = null
setAvatar(newAvatar) setAvatar(newAvatar)
}} }}
@@ -428,7 +424,7 @@ export default function AvatarInfo() {
width={904} width={904}
height={1260} height={1260}
priority priority
src={`${process.env.CDN_URL}/${lightconeDetail.image}`} src={`${process.env.CDN_URL}/${lightconeDetail?.Image?.ImagePath}`}
className="w-full h-full rounded-lg object-cover shadow-lg" className="w-full h-full rounded-lg object-cover shadow-lg"
alt="Lightcone" alt="Lightcone"
/> />
@@ -438,20 +434,20 @@ export default function AvatarInfo() {
{/* Lightcone Info & Controls */} {/* Lightcone Info & Controls */}
<div className="lg:col-span-2 space-y-6"> <div className="lg:col-span-2 space-y-6">
{/* Basic Info */} {/* Basic Info */}
{mapLightconeInfo[lightcone.item_id] && ( {lightconeDetail && (
<div className="bg-base-300 rounded-xl p-6 border border-base-content/10"> <div className="bg-base-300 rounded-xl p-6 border border-base-content/10">
<div className="flex flex-wrap items-center gap-3 mb-4"> <div className="flex flex-wrap items-center gap-3 mb-4">
<h3 className="text-2xl font-bold text-base-content"> <h3 className="text-2xl font-bold text-base-content">
<ParseText <ParseText
locale={locale} locale={locale}
text={mapLightconeInfo[lightcone.item_id].Name} text={getLocaleName(locale, lightconeDetail.Name)}
/> />
</h3> </h3>
<div className="badge badge-outline badge-lg"> <div className="badge badge-outline badge-lg">
{transI18n(mapLightconeInfo[lightcone.item_id].BaseType.toLowerCase())} {transI18n(lightconeDetail.BaseType.toLowerCase())}
</div> </div>
<div className="badge badge-outline badge-lg"> <div className="badge badge-outline badge-lg">
{calcRarity(mapLightconeInfo[lightcone.item_id].Rarity) + "⭐"} {calcRarity(lightconeDetail.Rarity) + "⭐"}
</div> </div>
<div className="badge badge-outline badge-lg"> <div className="badge badge-outline badge-lg">
{"id: " + lightcone.item_id} {"id: " + lightcone.item_id}
@@ -463,8 +459,8 @@ export default function AvatarInfo() {
className="text-base-content/80 leading-relaxed" className="text-base-content/80 leading-relaxed"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
mapLightconeInfo[lightcone.item_id].Refinements.Desc, getLocaleName(locale, lightconeDetail.Skills.Desc),
mapLightconeInfo[lightcone.item_id].Refinements.Level[lightcone.rank.toString()]?.ParamList || [] lightconeDetail.Skills.Level[lightcone.rank.toString()]?.Param || []
) )
}} }}
/> />
@@ -491,8 +487,7 @@ export default function AvatarInfo() {
<button <button
onClick={() => { onClick={() => {
if (avatarSelected) { if (avatarSelected) {
const newDefaultFilter = { path: [avatarSelected.baseType.toLowerCase()], rarity: [] } setResetDataLightcone(avatarSelected, baseType)
setDefaultFilter(newDefaultFilter)
handleShow("action_detail_modal") handleShow("action_detail_modal")
} }
}} }}

View File

@@ -2,18 +2,20 @@
import { getNameChar } from '@/helper'; import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import { CharacterBasic } from '@/types'; import { AvatarDetail } from '@/types';
import ParseText from '../parseText'; import ParseText from '../parseText';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import useDetailDataStore from '@/stores/detailDataStore';
interface CharacterCardProps { interface CharacterCardProps {
data: CharacterBasic data: AvatarDetail
} }
export default function CharacterCard({ data }: CharacterCardProps) { export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
const transI18n = useTranslations("DataPage"); const transI18n = useTranslations("DataPage");
const { baseType, damageType } = useDetailDataStore()
return ( return (
<li <li
@@ -23,7 +25,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
hover:scale-105 cursor-pointer min-h-45 sm:min-h-45 md:min-h-52.5 lg:min-h-55 xl:min-h-60 2xl:min-h-85" hover:scale-105 cursor-pointer min-h-45 sm:min-h-45 md:min-h-52.5 lg:min-h-55 xl:min-h-60 2xl:min-h-85"
> >
<div <div
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerAvatarRarityType5" className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerAvatarRarityType5"
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50" ? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
: "from-purple-400 via-purple-600/70 to-purple-800/50" : "from-purple-400 via-purple-600/70 to-purple-800/50"
}`} }`}
@@ -35,7 +37,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
height={512} height={512}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${data.icon}`} src={`${process.env.CDN_URL}/${data.Image.AvatarIconPath}`}
priority={true} priority={true}
className="rounded-md w-full h-full object-contain" className="rounded-md w-full h-full object-contain"
alt="ALT" alt="ALT"
@@ -45,18 +47,18 @@ export default function CharacterCard({ data }: CharacterCardProps) {
height={32} height={32}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${data.damageType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${damageType?.[data.DamageType].Icon}`}
className="absolute top-0 left-0 w-6 h-6 rounded-full" className="absolute top-0 left-0 w-6 h-6 rounded-full"
alt={data.damageType.toLowerCase()} alt={data.DamageType.toLowerCase()}
/> />
<Image <Image
width={32} width={32}
height={32} height={32}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${data.baseType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${baseType?.[data.BaseType].Icon}`}
className="absolute top-0 right-0 w-6 h-6 rounded-full" className="absolute top-0 right-0 w-6 h-6 rounded-full"
alt={data.baseType.toLowerCase()} alt={data.BaseType.toLowerCase()}
style={{ style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF" boxShadow: "inset 0 0 8px 4px #9CA3AF"
}} }}

View File

@@ -3,16 +3,14 @@
import React from 'react'; import React from 'react';
import { CharacterInfoCardType } from '@/types'; import { CharacterInfoCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import useAvatarStore from '@/stores/avatarStore';
import useLightconeStore from '@/stores/lightconeStore';
import Image from 'next/image'; import Image from 'next/image';
import ParseText from '../parseText'; import ParseText from '../parseText';
import useDetailDataStore from '@/stores/detailDataStore';
import { getLocaleName } from '@/helper/getName';
export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) { export default function CharacterInfoCard({ character, selectedCharacters, onCharacterToggle }: { character: CharacterInfoCardType, selectedCharacters: CharacterInfoCardType[], onCharacterToggle: (characterId: CharacterInfoCardType) => void }) {
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id); const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
const { mapAvatarInfo } = useAvatarStore(); const { mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore();
const { mapLightconeInfo } = useLightconeStore();
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
return ( return (
@@ -29,8 +27,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="relative mb-4"> <div className="relative mb-4">
<div className="w-full h-48 rounded-lg overflow-hidden relative"> <div className="w-full h-48 rounded-lg overflow-hidden relative">
<Image <Image
src={`${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${character.avatar_id}.png`} src={`${process.env.CDN_URL}/${mapAvatar?.[character.avatar_id.toString()]?.Image?.AvatarIconPath}`}
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""} alt={getLocaleName(locale, mapAvatar?.[character.avatar_id.toString()]?.Name)}
width={376} width={376}
height={512} height={512}
unoptimized unoptimized
@@ -42,18 +40,18 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
height={48} height={48}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${damageType?.[mapAvatar?.[character.avatar_id.toString()]?.DamageType || ""].Icon}`}
className="absolute top-0 left-0 w-10 h-10 rounded-full" className="absolute top-0 left-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()} alt={mapAvatar[character.avatar_id.toString()]?.DamageType.toLowerCase()}
/> />
<Image <Image
width={48} width={48}
height={48} height={48}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${baseType?.[mapAvatar?.[character.avatar_id.toString()]?.BaseType || ""].Icon}`}
className="absolute top-0 right-0 w-10 h-10 rounded-full" className="absolute top-0 right-0 w-10 h-10 rounded-full"
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()} alt={mapAvatar[character.avatar_id.toString()]?.BaseType.toLowerCase()}
style={{ style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF" boxShadow: "inset 0 0 8px 4px #9CA3AF"
}} }}
@@ -65,7 +63,7 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="w-full rounded-lg flex items-center justify-center mb-2"> <div className="w-full rounded-lg flex items-center justify-center mb-2">
<div className="text-center"> <div className="text-center">
<ParseText className="text-lg font-bold" <ParseText className="text-lg font-bold"
text={mapAvatarInfo[character.avatar_id.toString()]?.Name} text={getLocaleName(locale, mapAvatar[character.avatar_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
<div className="text-base mb-1">Lv.{character.level} E{character.rank}</div> <div className="text-base mb-1">Lv.{character.level} E{character.rank}</div>
@@ -101,8 +99,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${character.lightcone.item_id}.png`} src={`${process.env.CDN_URL}/${mapLightCone?.[character.lightcone.item_id.toString()]?.Image?.ImagePath}`}
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name} alt={getLocaleName(locale, mapLightCone?.[character.lightcone.item_id.toString()]?.Name)}
width={348} width={348}
height={408} height={408}
className="w-full h-full object-contain rounded-lg" className="w-full h-full object-contain rounded-lg"
@@ -113,7 +111,7 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="text-center"> <div className="text-center">
<div className="text-lg font-bold"> <div className="text-lg font-bold">
<ParseText <ParseText
text={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name} text={getLocaleName(locale, mapLightCone[character.lightcone.item_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
</div> </div>

View File

@@ -2,27 +2,27 @@
import { getLocaleName } from '@/helper'; import { getLocaleName } from '@/helper';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import { LightConeBasic } from '@/types';
import ParseText from '../parseText'; import ParseText from '../parseText';
import Image from 'next/image'; import Image from 'next/image';
import { LightConeDetail } from '@/types';
interface LightconeCardProps { interface LightconeCardProps {
data: LightConeBasic data: LightConeDetail
} }
export default function LightconeCard({ data }: LightconeCardProps) { export default function LightconeCard({ data }: LightconeCardProps) {
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
const text = getLocaleName(locale, data) const text = getLocaleName(locale, data.Name)
return ( return (
<li className="z-10 flex flex-col items-center rounded-md shadow-lg <li className="z-10 flex flex-col items-center rounded-md shadow-lg
bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300 bg-linear-to-b from-customStart to-customEnd transform transition-transform duration-300
hover:scale-105 cursor-pointer min-h-55" hover:scale-105 cursor-pointer min-h-55"
> >
<div <div
className={`w-full rounded-md bg-linear-to-br ${data.rank === "CombatPowerLightconeRarity5" className={`w-full rounded-md bg-linear-to-br ${data.Rarity === "CombatPowerLightconeRarity5"
? "from-yellow-400 via-yellow-600/70 to-yellow-800/50" ? "from-yellow-400 via-yellow-600/70 to-yellow-800/50"
: data.rank === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" : : data.Rarity === "CombatPowerLightconeRarity4" ? "from-purple-400 via-purple-600/70 to-purple-800/50" :
"from-blue-400 via-blue-600/70 to-blue-800/50" "from-blue-400 via-blue-600/70 to-blue-800/50"
}`} }`}
> >
@@ -30,7 +30,7 @@ export default function LightconeCard({ data }: LightconeCardProps) {
<div className="relative w-full h-full"> <div className="relative w-full h-full">
<Image <Image
loading="lazy" loading="lazy"
src={`${process.env.CDN_URL}/${data.thumbnail}`} src={`${process.env.CDN_URL}/${data?.Image?.ThumbnailPath}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={348} width={348}

View File

@@ -3,14 +3,15 @@
import React from 'react'; import React from 'react';
import { AvatarProfileCardType } from '@/types'; import { AvatarProfileCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore'; import useLocaleStore from '@/stores/localeStore';
import useLightconeStore from '@/stores/lightconeStore';
import Image from 'next/image'; import Image from 'next/image';
import ParseText from '../parseText'; import ParseText from '../parseText';
import useDetailDataStore from '@/stores/detailDataStore';
import { getLocaleName } from '@/helper/getName';
export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) { export default function ProfileCard({ profile, selectedProfile, onProfileToggle }: { profile: AvatarProfileCardType, selectedProfile: AvatarProfileCardType[], onProfileToggle: (profileId: AvatarProfileCardType) => void }) {
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key); const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
const { mapLightconeInfo } = useLightconeStore(); const { mapLightCone } = useDetailDataStore();
const { locale } = useLocaleStore(); const { locale } = useLocaleStore();
return ( return (
@@ -30,7 +31,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`} src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${profile.lightcone.item_id}.png`}
alt={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name} alt={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
width={348} width={348}
height={408} height={408}
className="w-full h-full object-contain rounded-lg" className="w-full h-full object-contain rounded-lg"
@@ -41,7 +42,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
<div className="text-center"> <div className="text-center">
<div className="text-lg font-bold"> <div className="text-lg font-bold">
<ParseText <ParseText
text={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name} text={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
locale={locale} locale={locale}
/> />
</div> </div>
@@ -59,7 +60,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.webp`} src={`${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${relic.relic_set_id}_${relic.relic_id.toString()[relic.relic_id.toString().length - 1]}.png`}
alt="Relic" alt="Relic"
width={124} width={124}
height={124} height={124}

View File

@@ -1,29 +1,56 @@
"use client"; "use client"
import { import {
useFetchASData, useFetchASGroupData,
useFetchAvatarData, useFetchAvatarData,
useFetchChangelog, useFetchChangelog,
useFetchConfigData, useFetchConfigData,
useFetchLightconeData, useFetchLightconeData,
useFetchMOCData, useFetchMOCGroupData,
useFetchMonsterData, useFetchMonsterData,
useFetchPEAKData, useFetchPeakGroupData,
useFetchPFData, useFetchPFGroupData,
useFetchRelicData useFetchRelicSetData
} from "@/lib/hooks"; } from "@/lib/hooks"
export default function ClientDataFetcher() { export default function ClientDataFetcher({
useFetchConfigData(); children
useFetchAvatarData(); }: {
useFetchLightconeData(); children: React.ReactNode
useFetchRelicData(); }) {
useFetchMonsterData(); const q1 = useFetchConfigData()
useFetchPFData(); const q2 = useFetchAvatarData()
useFetchMOCData(); const q3 = useFetchLightconeData()
useFetchASData(); const q4 = useFetchRelicSetData()
useFetchPEAKData(); const q5 = useFetchMonsterData()
useFetchChangelog(); const q6 = useFetchPFGroupData()
const q7 = useFetchMOCGroupData()
const q8 = useFetchASGroupData()
const q9 = useFetchPeakGroupData()
const q10 = useFetchChangelog()
return null; const queries = [q1,q2,q3,q4,q5,q6,q7,q8,q9,q10]
}
const loading = queries.some(q => q.isLoading)
const progress =
(queries.filter(q => q.isSuccess).length / queries.length) * 100
if (loading) {
return (
<div className="flex h-screen flex-col items-center justify-center gap-4">
<div className="text-lg font-semibold">Loading data...</div>
<progress
className="progress progress-primary w-56"
value={progress}
max="100"
/>
<div>{Math.floor(progress)}%</div>
</div>
)
}
return <>{children}</>
}

View File

@@ -1,29 +1,28 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client" "use client"
import { replaceByParam } from "@/helper"; import { replaceByParam, getLocaleName } from '@/helper';
import useListAvatarStore from "@/stores/avatarStore";
import Image from "next/image"; import Image from "next/image";
import ParseText from "../parseText"; import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useMemo } from "react"; import { useMemo } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useCurrentDataStore from "@/stores/currentDataStore";
export default function EidolonsInfo() { export default function EidolonsInfo() {
const { avatarSelected, mapAvatarInfo } = useListAvatarStore() const { avatarSelected } = useCurrentDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { setAvatars, avatars } = useUserDataStore() const { setAvatars, avatars } = useUserDataStore()
const charRank = useMemo(() => { const charRank = useMemo(() => {
if (!avatarSelected) return null; if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id]; const avatar = avatars[avatarSelected.ID];
if (avatar?.enhanced != "") { if (avatar?.enhanced != "") {
return mapAvatarInfo[avatarSelected.id]?.Enhanced[avatar?.enhanced].Ranks return avatarSelected?.Enhanced?.[avatar?.enhanced]?.Ranks
} }
return mapAvatarInfo[avatarSelected.id]?.Ranks return avatarSelected?.Ranks
}, [avatarSelected, avatars, locale, mapAvatarInfo]); }, [avatarSelected, avatars]);
return ( return (
<div className="bg-base-100 rounded-xl p-6 shadow-lg"> <div className="bg-base-100 rounded-xl p-6 shadow-lg">
@@ -32,22 +31,22 @@ export default function EidolonsInfo() {
{transI18n("eidolons")} {transI18n("eidolons")}
</h2> </h2>
<div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden"> <div className="grid grid-cols-1 m-4 p-4 font-bold gap-4 w-fit max-h-[77vh] min-h-[50vh] overflow-y-scroll overflow-x-hidden">
{charRank && avatars[avatarSelected?.id || ""] && ( {charRank && avatars[avatarSelected?.ID || ""] && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(charRank || {}).map(([key, rank]) => ( {Object.entries(charRank || {}).map(([key, rank]) => (
<div key={key} <div key={key}
className="flex flex-col items-center cursor-pointer hover:scale-105" className="flex flex-col items-center cursor-pointer hover:scale-105"
onClick={() => { onClick={() => {
let newRank = Number(key) let newRank = Number(key)
if (avatars[avatarSelected?.id || ""]?.data?.rank == Number(key)) { if (avatars[avatarSelected?.ID || ""]?.data?.rank == Number(key)) {
newRank = Number(key) - 1 newRank = Number(key) - 1
} }
setAvatars({ ...avatars, [avatarSelected?.id || ""]: { ...avatars[avatarSelected?.id || ""], data: { ...avatars[avatarSelected?.id || ""].data, rank: newRank } } }) setAvatars({ ...avatars, [avatarSelected?.ID || ""]: { ...avatars[avatarSelected?.ID || ""], data: { ...avatars[avatarSelected?.ID || ""].data, rank: newRank } } })
}} }}
> >
<Image <Image
className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.id || ""]?.data?.rank ? "" : "grayscale"}`} className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.ID.toString() || ""]?.data?.rank ? "" : "grayscale"}`}
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.png`} src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.ID}/${avatarSelected?.ID}_Rank_${key}.png`}
alt={`Rank ${key}`} alt={`Rank ${key}`}
priority priority
unoptimized unoptimized
@@ -60,12 +59,12 @@ export default function EidolonsInfo() {
<span className="inline-block text-indigo-500">{key}.</span> <span className="inline-block text-indigo-500">{key}.</span>
<ParseText <ParseText
locale={locale} locale={locale}
text={rank.Name} text={getLocaleName(locale, rank.Name)}
className="text-center text-base font-normal leading-tight" className="text-center text-base font-normal leading-tight"
/> />
</div> </div>
<div className="text-sm font-normal"> <div className="text-sm font-normal">
<div dangerouslySetInnerHTML={{ __html: replaceByParam(rank.Desc, rank.ParamList) }} /> <div dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, rank.Desc), rank.Param) }} />
</div> </div>
</div> </div>
))} ))}

View File

@@ -3,34 +3,28 @@ import { motion } from "framer-motion"
import { EyeOff, Eye, Hammer, RefreshCw, ShieldBan, User, Swords, SkipForward, BowArrow, Info, RouteIcon, Search } from "lucide-react" import { EyeOff, Eye, Hammer, RefreshCw, ShieldBan, User, Swords, SkipForward, BowArrow, Info, RouteIcon, Search } from "lucide-react"
import useGlobalStore from '@/stores/globalStore' import useGlobalStore from '@/stores/globalStore'
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import useEventStore from "@/stores/eventStore"
import { getLocaleName, getNameChar } from "@/helper" import { getLocaleName, getNameChar } from "@/helper"
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import useAvatarStore from "@/stores/avatarStore"
import SelectCustomImage from "../select/customSelectImage" import SelectCustomImage from "../select/customSelectImage"
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import useMazeStore from "@/stores/mazeStore" import useDetailDataStore from "@/stores/detailDataStore"
export default function ExtraSettingBar() { export default function ExtraSettingBar() {
const { extraData, setExtraData } = useGlobalStore() const { extraData, setExtraData } = useGlobalStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { PEAKEvent } = useEventStore() const { mapAvatar, mapPeak, stage, baseType } = useDetailDataStore()
const { listAvatar } = useAvatarStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const [showSearchStage, setShowSearchStage] = useState(false) const [showSearchStage, setShowSearchStage] = useState(false)
const [isChildClick, setIsChildClick] = useState(false) const [isChildClick, setIsChildClick] = useState(false)
const [stageSearchTerm, setStageSearchTerm] = useState("") const [stageSearchTerm, setStageSearchTerm] = useState("")
const [stagePage, setStagePage] = useState(1) const [stagePage, setStagePage] = useState(1)
const { Stage } = useMazeStore()
const pageSize = 30 const pageSize = 30
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({ const stageList = useMemo(() => Object.values(stage), [stage])
id: stage.stage_id.toString(),
name: `${stage.stage_type} (${stage.stage_id})`,
})), [Stage])
const filteredStages = useMemo(() => stageList.filter((s) => const filteredStages = useMemo(() => stageList.filter((s) =>
s.name.toLowerCase().includes(stageSearchTerm.toLowerCase()) getLocaleName(locale, s.Name).toLowerCase().includes(stageSearchTerm.toLowerCase())
), [stageList, stageSearchTerm]) ), [stageList, stageSearchTerm, locale])
const paginatedStages = useMemo(() => filteredStages.slice( const paginatedStages = useMemo(() => filteredStages.slice(
(stagePage - 1) * pageSize, (stagePage - 1) * pageSize,
@@ -122,7 +116,7 @@ export default function ExtraSettingBar() {
}} }}
> >
<Search className="w-6 h-6" /> <Search className="w-6 h-6" />
<span className="text-left"> {transI18n("stage")}: {stageList.find((s) => s.id === extraData?.theory_craft?.stage_id?.toString())?.name || transI18n("selectStage")}</span> <span className="text-left"> {transI18n("stage")}: {getLocaleName(locale, stageList.find((s) => s.ID.toString() === extraData?.theory_craft?.stage_id?.toString())?.Name) || transI18n("selectStage")}</span>
</button> </button>
</div> </div>
{showSearchStage && ( {showSearchStage && (
@@ -158,17 +152,17 @@ export default function ExtraSettingBar() {
<> <>
{paginatedStages.map((stage) => ( {paginatedStages.map((stage) => (
<div <div
key={stage.id} key={stage.ID}
className="p-2 hover:bg-primary/20 rounded cursor-pointer" className="p-2 hover:bg-primary/20 rounded cursor-pointer"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
setIsChildClick(true) setIsChildClick(true)
if (extraData?.theory_craft?.stage_id !== Number(stage.id)) { if (extraData?.theory_craft?.stage_id !== Number(stage.ID)) {
setExtraData({ setExtraData({
...extraData, ...extraData,
theory_craft: { theory_craft: {
stage_id: Number(stage.id), stage_id: Number(stage.ID),
cycle_count: extraData?.theory_craft?.cycle_count || 1, cycle_count: extraData?.theory_craft?.cycle_count || 1,
mode: extraData?.theory_craft?.mode || false, mode: extraData?.theory_craft?.mode || false,
hp: extraData?.theory_craft?.hp || {} hp: extraData?.theory_craft?.hp || {}
@@ -179,7 +173,7 @@ export default function ExtraSettingBar() {
onChangeSearch("") onChangeSearch("")
}} }}
> >
{stage.name} {getLocaleName(locale, stage.Name)}
</div> </div>
))} ))}
@@ -238,10 +232,10 @@ export default function ExtraSettingBar() {
<User className="text-warning" size={20} /> <User className="text-warning" size={20} />
<span className="label-text font-semibold">{transI18n("mainPath")}</span> <span className="label-text font-semibold">{transI18n("mainPath")}</span>
<SelectCustomImage <SelectCustomImage
customSet={listAvatar.filter((it) => extraData?.multi_path?.multi_path_main?.includes(Number(it.id))).map((it) => ({ customSet={Object.values(mapAvatar).filter((it) => extraData?.multi_path?.multi_path_main?.includes(Number(it.ID))).map((it) => ({
value: it.id, value: it.ID.toString(),
label: getNameChar(locale, transI18n, it), label: getNameChar(locale, transI18n, it),
imageUrl: `/icon/${it.baseType.toLowerCase()}.webp` imageUrl: `${process.env.CDN_URL}/${baseType?.[it.BaseType].Icon}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.main?.toString() || ""} selectedCustomSet={extraData?.multi_path?.main?.toString() || ""}
@@ -268,10 +262,10 @@ export default function ExtraSettingBar() {
<BowArrow className="text-info" size={20} /> <BowArrow className="text-info" size={20} />
<span className="label-text font-semibold">{transI18n("march7Path")}</span> <span className="label-text font-semibold">{transI18n("march7Path")}</span>
<SelectCustomImage <SelectCustomImage
customSet={listAvatar.filter((it) => extraData?.multi_path?.multi_path_march_7?.includes(Number(it.id))).map((it) => ({ customSet={Object.values(mapAvatar).filter((it) => extraData?.multi_path?.multi_path_march_7?.includes(Number(it.ID))).map((it) => ({
value: it.id, value: it.ID.toString(),
label: getNameChar(locale, transI18n, it), label: getNameChar(locale, transI18n, it),
imageUrl: `/icon/${it.baseType.toLowerCase()}.webp` imageUrl: `${process.env.CDN_URL}/${baseType?.[it.BaseType].Icon}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""} selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""}
@@ -318,9 +312,9 @@ export default function ExtraSettingBar() {
}) })
} }
> >
{PEAKEvent.filter(event => extraData?.challenge?.challenge_peak_group_id_list?.includes(Number(event.id))).map(event => ( {Object.values(mapPeak).filter(event => extraData?.challenge?.challenge_peak_group_id_list?.includes(Number(event.ID))).map(event => (
<option key={event.id} value={event.id}> <option key={event.ID} value={event.ID}>
{getLocaleName(locale, event)} ({event.id}) {getLocaleName(locale, event.Name)} ({event.ID})
</option> </option>
))} ))}
</select> </select>

View File

@@ -8,7 +8,6 @@ import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import EnkaImport from "../importBar/enka"; import EnkaImport from "../importBar/enka";
@@ -564,19 +563,6 @@ export default function Header() {
</ul> </ul>
</div> </div>
</div> </div>
{/* GitHub Link */}
<Link
className='flex btn btn-ghost btn-sm btn-circle bg-white/20 hover:bg-white transition-all duration-200 items-center justify-center tooltip tooltip-bottom'
href={"https://github.com/AzenKain/Firefly-Srtools"}
target="_blank"
rel="noopener noreferrer"
data-tip="Github"
>
<svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>
</Link>
</div> </div>
{modalConfigs?.map(({ id, title, onClose, content }) => ( {modalConfigs?.map(({ id, title, onClose, content }) => (

View File

@@ -1,27 +1,26 @@
"use client" "use client"
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useEffect, useState } from "react"; import { useState, useMemo } from "react";
import useCopyProfileStore from "@/stores/copyProfile"; import useCopyProfileStore from "@/stores/copyProfile";
import ProfileCard from "../card/profileCard"; import ProfileCard from "../card/profileCard";
import { AvatarProfileCardType, AvatarProfileStore } from "@/types"; import { AvatarProfileCardType, AvatarProfileStore } from "@/types";
import Image from "next/image"; import Image from "next/image";
import useListAvatarStore from "@/stores/avatarStore"; import { getNameChar, calcRarity } from "@/helper";
import { getNameChar } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import SelectCustomImage from "../select/customSelectImage"; import SelectCustomImage from "../select/customSelectImage";
import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore";
export default function CopyImport() { export default function CopyImport() {
const { avatars, setAvatar } = useUserDataStore(); const { avatars, setAvatar } = useUserDataStore();
const { avatarSelected } = useListAvatarStore() const { avatarSelected } = useCurrentDataStore()
const { mapAvatar, baseType, damageType } = useDetailDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
selectedProfiles, selectedProfiles,
listCopyAvatar,
avatarCopySelected, avatarCopySelected,
setSelectedProfiles, setSelectedProfiles,
filterCopy,
setFilterCopy,
setAvatarCopySelected, setAvatarCopySelected,
listElement, listElement,
listPath, listPath,
@@ -37,6 +36,22 @@ export default function CopyImport() {
text: "" text: ""
}) })
const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return []
let list = Object.values(mapAvatar);
const allElementFalse = !Object.values(listElement).some(v => v)
const allPathFalse = !Object.values(listPath).some(v => v)
const allRarityFalse = !Object.values(listRank).some(v => v)
list = list.filter(item => (allElementFalse || listElement[item.DamageType]) && (allPathFalse || listPath[item.BaseType]) && (allRarityFalse || listRank[calcRarity(item.Rarity)]))
list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r
return a.ID - b.ID
})
return list
}, [mapAvatar, listElement, listPath, listRank, locale, transI18n])
const handleProfileToggle = (profile: AvatarProfileCardType) => { const handleProfileToggle = (profile: AvatarProfileCardType) => {
if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) { if (selectedProfiles.some((selectedProfile) => selectedProfile.key === profile.key)) {
setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key)); setSelectedProfiles(selectedProfiles.filter((selectedProfile) => selectedProfile.key !== profile.key));
@@ -45,17 +60,6 @@ export default function CopyImport() {
setSelectedProfiles([...selectedProfiles, profile]); setSelectedProfiles([...selectedProfiles, profile]);
}; };
useEffect(() => {
setFilterCopy({
...filterCopy,
locale: locale,
path: Object.keys(listPath).filter((key) => listPath[key]),
element: Object.keys(listElement).filter((key) => listElement[key]),
rarity: Object.keys(listRank).filter((key) => listRank[key])
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPath, listRank, listElement, locale, setFilterCopy])
const clearSelection = () => { const clearSelection = () => {
setSelectedProfiles([]); setSelectedProfiles([]);
@@ -67,7 +71,7 @@ export default function CopyImport() {
const selectAll = () => { const selectAll = () => {
if (avatarCopySelected) { if (avatarCopySelected) {
setSelectedProfiles(avatars[avatarCopySelected?.id.toString()].profileList.map((profile, index) => { setSelectedProfiles(avatars[avatarCopySelected?.ID.toString()].profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) { if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null; return null;
} }
@@ -106,20 +110,20 @@ export default function CopyImport() {
return; return;
} }
const newListProfile = avatars[avatarCopySelected.id.toString()].profileList.map((profile) => { const newListProfile = avatars[avatarCopySelected.ID.toString()].profileList.map((profile) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) { if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null; return null;
} }
return { return {
...profile, ...profile,
profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.id}`, profile_name: profile.profile_name + ` - Copy: ${avatarCopySelected?.ID}`,
} as AvatarProfileStore } as AvatarProfileStore
}).filter((profile) => profile !== null); }).filter((profile) => profile !== null);
const newAvatar = { const newAvatar = {
...avatars[avatarSelected.id.toString()], ...avatars[avatarSelected?.ID?.toString()],
profileList: avatars[avatarSelected.id.toString()].profileList.concat(newListProfile), profileList: avatars[avatarSelected?.ID?.toString()].profileList.concat(newListProfile),
profileSelect: avatars[avatarSelected.id.toString()].profileList.length - 1, profileSelect: avatars[avatarSelected?.ID?.toString()].profileList.length - 1,
} }
setAvatar(newAvatar); setAvatar(newAvatar);
setSelectedProfiles([]); setSelectedProfiles([]);
@@ -143,9 +147,9 @@ export default function CopyImport() {
{/* Path */} {/* Path */}
<div> <div>
<div className="flex flex-wrap gap-2 justify-start items-center"> <div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(listPath).map(([key], index) => ( {Object.entries(baseType).map(([key, value]) => (
<div <div
key={index} key={key}
onClick={() => { onClick={() => {
setListPath({ ...listPath, [key]: !listPath[key] }) setListPath({ ...listPath, [key]: !listPath[key] })
}} }}
@@ -156,7 +160,7 @@ export default function CopyImport() {
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${key}.webp`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-8 w-8 object-contain rounded-md" className="h-8 w-8 object-contain rounded-md"
width={200} width={200}
@@ -170,9 +174,9 @@ export default function CopyImport() {
{/* Element */} {/* Element */}
<div> <div>
<div className="flex flex-wrap gap-2 justify-start items-center"> <div className="flex flex-wrap gap-2 justify-start items-center">
{Object.entries(listElement).map(([key], index) => ( {Object.entries(damageType).map(([key, value]) => (
<div <div
key={index} key={key}
onClick={() => { onClick={() => {
setListElement({ ...listElement, [key]: !listElement[key] }) setListElement({ ...listElement, [key]: !listElement[key] })
}} }}
@@ -183,7 +187,7 @@ export default function CopyImport() {
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${key}.webp`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md" className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md"
width={200} width={200}
@@ -217,19 +221,19 @@ export default function CopyImport() {
</div> </div>
<div className="grid grid-cols-1 gap-2"> <div className="grid grid-cols-1 gap-2">
{listCopyAvatar.length > 0 && ( {listAvatar.length > 0 && (
<div> <div>
<div>{transI18n("characterName")}</div> <div>{transI18n("characterName")}</div>
<SelectCustomImage <SelectCustomImage
customSet={listCopyAvatar.map((avatar) => ({ customSet={listAvatar.map((avatar) => ({
value: avatar.id.toString(), value: avatar.ID.toString(),
label: getNameChar(locale, transI18n, avatar), label: getNameChar(locale, transI18n, avatar),
imageUrl: `${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${avatar.id}.png` imageUrl: `${process.env.CDN_URL}/${avatar.Image.AvatarIconPath}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={avatarCopySelected?.id.toString() || ""} selectedCustomSet={avatarCopySelected?.ID.toString() || ""}
placeholder="Character Select" placeholder="Character Select"
setSelectedCustomSet={(value) => setAvatarCopySelected(listCopyAvatar.find((avatar) => avatar.id.toString() === value) || null)} setSelectedCustomSet={(value) => setAvatarCopySelected(mapAvatar[value] || null)}
/> />
</div> </div>
)} )}
@@ -270,7 +274,7 @@ export default function CopyImport() {
{/* Character Grid */} {/* Character Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{avatarCopySelected && avatars[avatarCopySelected?.id.toString()]?.profileList.map((profile, index) => { {avatarCopySelected && avatars[avatarCopySelected?.ID.toString()]?.profileList.map((profile, index) => {
if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) { if (!profile.lightcone?.item_id && Object.keys(profile.relics).length == 0) {
return null; return null;
} }

View File

@@ -1,58 +1,59 @@
"use client" "use client"
import { useEffect } from "react" import { useMemo } from "react"
import Image from "next/image"; import Image from "next/image";
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import useLightconeStore from "@/stores/lightconeStore";
import LightconeCard from "../card/lightconeCard"; import LightconeCard from "../card/lightconeCard";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useAvatarStore from "@/stores/avatarStore";
import useModelStore from "@/stores/modelStore"; import useModelStore from "@/stores/modelStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { calcRarity, getLocaleName } from "@/helper";
export default function LightconeBar() { export default function LightconeBar() {
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
listLightcone, avatarSelected,
filter, mapLightconePathActive,
setFilter, mapLightconeRankActive,
defaultFilter, setMapLightconePathActive,
listPath, setMapLightconeRankActive,
listRank, lightconeSearch,
setListPath, setLightconeSearch
setListRank } = useCurrentDataStore()
} = useLightconeStore()
const { setAvatar, avatars } = useUserDataStore() const { setAvatar, avatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
const { setIsOpenLightcone } = useModelStore() const { setIsOpenLightcone } = useModelStore()
const { mapLightCone, baseType } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
useEffect(() => { const listLightcone = useMemo(() => {
const newListPath: Record<string, boolean> = { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, "elation": false } if (!mapLightCone || !locale) return []
const newListRank: Record<string, boolean> = { "3": false, "4": false, "5": false }
for (const path of defaultFilter.path) {
if (path in newListPath) {
newListPath[path] = true
}
}
for (const rarity of defaultFilter.rarity) {
if (rarity in newListRank) {
newListRank[rarity] = true
}
}
setListPath(newListPath)
setListRank(newListRank)
}, [defaultFilter, setListPath, setListRank])
useEffect(() => { let list = Object.values(mapLightCone)
setFilter({
...filter, if (lightconeSearch) {
locale: locale, list = list.filter(item => getLocaleName(locale, item.Name).toLowerCase().includes(lightconeSearch.toLowerCase()))
path: Object.keys(listPath).filter((key) => listPath[key]), }
rarity: Object.keys(listRank).filter((key) => listRank[key])
const allRankFalse = !Object.values(mapLightconeRankActive).some(v => v)
const allPathFalse = !Object.values(mapLightconePathActive).some(v => v)
list = list.filter(item =>
(allRankFalse || mapLightconeRankActive[item.Rarity]) &&
(allPathFalse || mapLightconePathActive[item.BaseType])
)
list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r
return a.ID - b.ID
}) })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPath, listRank, locale]) return list
}, [mapLightCone, mapLightconePathActive, mapLightconeRankActive, lightconeSearch, locale])
return ( return (
<div> <div>
@@ -65,8 +66,8 @@ export default function LightconeBar() {
<div className="flex items-start flex-col gap-2"> <div className="flex items-start flex-col gap-2">
<div>Search</div> <div>Search</div>
<input <input
value={filter.name} value={lightconeSearch}
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })} onChange={(e) => setLightconeSearch(e.target.value)}
type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full" type="text" placeholder="LightCone Name" className="input input-accent mt-1 w-full"
/> />
</div> </div>
@@ -74,21 +75,21 @@ export default function LightconeBar() {
<div>Filter</div> <div>Filter</div>
<div className="flex flex-row flex-wrap justify-between mt-1 w-full"> <div className="flex flex-row flex-wrap justify-between mt-1 w-full">
<div className="flex flex-wrap mb-1 mx-1 gap-2"> <div className="flex flex-wrap mb-1 mx-1 gap-2">
{Object.keys(listPath).map((key, index) => ( {Object.entries(baseType).map(([key, value]) => (
<div <div
key={index} key={key}
onClick={() => { onClick={() => {
setListPath({ ...listPath, [key]: !listPath[key] }) setMapLightconePathActive({ ...mapLightconePathActive, [key]: !mapLightconePathActive[key] })
}} }}
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280" backgroundColor: mapLightconePathActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${key}.webp`} src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key} alt={key}
className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md" className="h-7 w-7 md:h-8 md:w-8 object-contain rounded-md"
width={200} width={200}
@@ -99,15 +100,15 @@ export default function LightconeBar() {
</div> </div>
<div className="flex flex-wrap mb-1 mx-1 gap-2"> <div className="flex flex-wrap mb-1 mx-1 gap-2">
{Object.keys(listRank).map((key, index) => ( {Object.keys(mapLightconeRankActive).map((key, index) => (
<div <div
key={index} key={index}
onClick={() => { onClick={() => {
setListRank({ ...listRank, [key]: !listRank[key] }) setMapLightconeRankActive({ ...mapLightconeRankActive, [key]: !mapLightconeRankActive[key] })
}} }}
className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" className="h-9.5 w-9.5 md:h-12.5 md:w-12.5 hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer"
style={{ style={{
backgroundColor: listRank[key] ? "#374151" : "#6B7280" backgroundColor: mapLightconeRankActive[key] ? "#374151" : "#6B7280"
}} }}
> >
<div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center"> <div className="font-bold text-white h-8 w-8 text-center flex items-center justify-center">
@@ -124,10 +125,10 @@ export default function LightconeBar() {
{listLightcone.map((item, index) => ( {listLightcone.map((item, index) => (
<div key={index} onClick={() => { <div key={index} onClick={() => {
if (avatarSelected) { if (avatarSelected) {
const avatar = avatars[avatarSelected.id] const avatar = avatars[avatarSelected?.ID?.toString()]
avatar.profileList[avatar.profileSelect].lightcone = { avatar.profileList[avatar.profileSelect].lightcone = {
level: 80, level: 80,
item_id: Number(item.id), item_id: item.ID,
rank: 1, rank: 1,
promotion: 6 promotion: 6
} }

View File

@@ -1,60 +1,50 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import useEventStore from "@/stores/eventStore"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import { getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useMonsterStore from "@/stores/monsterStore";
import Image from "next/image"; import Image from "next/image";
import { MonsterStore } from "@/types"; import { MonsterStore } from "@/types";
import useMazeStore from "@/stores/mazeStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore";
export default function AsBar() { export default function AsBar() {
const { ASEvent, mapASInfo } = useEventStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
as_config, as_config,
setAsConfig setAsConfig
} = useUserDataStore() } = useUserDataStore()
const { AS } = useMazeStore() const { mapMonster, mapAS, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapASInfo[as_config.event_id.toString()]?.Level.find((as) => as.Id === as_config.challenge_id) return mapAS[as_config.event_id.toString()]?.Level.find((as) => as.ID === as_config.challenge_id)
}, [as_config, mapASInfo]) }, [as_config, mapAS])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapASInfo[as_config.event_id.toString()] return mapAS[as_config.event_id.toString()]
}, [as_config, mapASInfo]) }, [as_config, mapAS])
const buffList = useMemo(() => { const buffList = useMemo(() => {
const challenge = AS[as_config.event_id.toString()]; if (!eventSelected) return [];
if (!challenge) return { buffList: [], buffId: [] };
if (as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") { if (as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") {
return { return eventSelected?.BuffList1 ?? [];
buffList: eventSelected?.BuffList1 ?? [],
buffId: challenge.buff_1 ?? [],
};
} }
if (as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") { if (as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") {
return { return eventSelected?.BuffList2 ?? [];
buffList: eventSelected?.BuffList2 ?? [],
buffId: challenge.buff_2 ?? [],
};
} }
return { buffList: [], buffId: [] }; return [];
}, [AS, as_config.event_id, as_config.floor_side, eventSelected?.BuffList1, eventSelected?.BuffList2]); }, [as_config.floor_side, eventSelected]);
useEffect(() => { useEffect(() => {
if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return if (!challengeSelected || as_config.event_id === 0 || as_config.challenge_id === 0) return
const newBattleConfig = structuredClone(as_config) const newBattleConfig = structuredClone(as_config)
newBattleConfig.cycle_count = 0 newBattleConfig.cycle_count = challengeSelected.TurnLimit
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (as_config.buff_id !== 0) { if (as_config.buff_id !== 0) {
@@ -63,24 +53,27 @@ export default function AsBar() {
level: 1 level: 1
}) })
} }
if (AS[as_config.challenge_id.toString()]) {
newBattleConfig.blessings.push({ if (challengeSelected) {
id: Number(AS[as_config.challenge_id.toString()].maze_buff), challengeSelected.MazeBuff.map((item) => {
level: 1 newBattleConfig.blessings.push({
id: item.ID,
level: 1
})
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
if ((as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower") if ((as_config.floor_side === "Upper" || as_config.floor_side === "Upper -> Lower")
&& challengeSelected.EventIDList1.length > 0) { && challengeSelected.EventList1.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventIDList1[0].StageID newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
for (const wave of challengeSelected.EventIDList1[0].MonsterList) { for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList1[0].Level, level: challengeSelected.EventList1[0].Level,
amount: 1, amount: 1,
}) })
} }
@@ -88,14 +81,14 @@ export default function AsBar() {
} }
} }
if ((as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper") if ((as_config.floor_side === "Lower" || as_config.floor_side === "Lower -> Upper")
&& challengeSelected.EventIDList2.length > 0) { && challengeSelected.EventList2.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventIDList2[0].StageID newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
for (const wave of challengeSelected.EventIDList2[0].MonsterList) { for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList2[0].Level, level: challengeSelected.EventList2[0].Level,
amount: 1, amount: 1,
}) })
} }
@@ -103,26 +96,26 @@ export default function AsBar() {
} }
} }
if (as_config.floor_side === "Lower -> Upper" if (as_config.floor_side === "Lower -> Upper"
&& challengeSelected.EventIDList1.length > 0) { && challengeSelected.EventList1.length > 0) {
for (const wave of challengeSelected.EventIDList1[0].MonsterList) { for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList1[0].Level, level: challengeSelected.EventList1[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} else if (as_config.floor_side === "Upper -> Lower" } else if (as_config.floor_side === "Upper -> Lower"
&& challengeSelected.EventIDList2.length > 0) { && challengeSelected.EventList2.length > 0) {
for (const wave of challengeSelected.EventIDList2[0].MonsterList) { for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList2[0].Level, level: challengeSelected.EventList2[0].Level,
amount: 1, amount: 1,
}) })
} }
@@ -130,19 +123,17 @@ export default function AsBar() {
} }
} }
setAsConfig(newBattleConfig) setAsConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
challengeSelected, challengeSelected,
mapAS,
as_config.event_id, as_config.event_id,
as_config.challenge_id, as_config.challenge_id,
as_config.floor_side, as_config.floor_side,
as_config.buff_id, as_config.buff_id,
mapASInfo,
AS,
]) ])
if (!ASEvent) return null if (!mapAS) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
@@ -150,10 +141,10 @@ export default function AsBar() {
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={ASEvent.map((as) => ({ customSet={Object.values(mapAS).sort((a, b) => b.ID - a.ID).map((as) => ({
id: as.id, id: as.ID.toString(),
name: getLocaleName(locale, as), name: getLocaleName(locale, as.Name),
time: `${as.begin} - ${as.end}`, time: `${as.BeginTime} - ${as.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={as_config.event_id.toString()} selectedCustomSet={as_config.event_id.toString()}
@@ -161,7 +152,7 @@ export default function AsBar() {
setSelectedCustomSet={(id) => setAsConfig({ setSelectedCustomSet={(id) => setAsConfig({
...as_config, ...as_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapASInfo[Number(id)]?.Level.slice(-1)[0]?.Id || 0, challenge_id: mapAS[Number(id)]?.Level.at(-1)?.ID || 0,
buff_id: 0 buff_id: 0
})} })}
/> />
@@ -179,8 +170,8 @@ export default function AsBar() {
onChange={(e) => setAsConfig({ ...as_config, challenge_id: Number(e.target.value) })} onChange={(e) => setAsConfig({ ...as_config, challenge_id: Number(e.target.value) })}
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{mapASInfo[as_config.event_id.toString()]?.Level.map((as) => ( {eventSelected?.Level.map((as) => (
<option key={as.Id} value={as.Id}>{as.Id % 10}</option> <option key={as.ID} value={as.ID}>{getLocaleName(locale, as.Name)}</option>
))} ))}
</select> </select>
</div> </div>
@@ -205,15 +196,11 @@ export default function AsBar() {
{eventSelected && ( {eventSelected && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={ customSet={buffList.map((buff) => ({
Array.isArray(buffList?.buffList) && Array.isArray(buffList?.buffId) id: buff.ID?.toString() || "",
? buffList.buffList.map((buff, index) => ({ name: getLocaleName(locale, buff?.Name) || "",
id: buffList.buffId?.[index]?.toString() || "", description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
name: buff?.Name || "", }))}
description: replaceByParam(buff?.Desc || "", buff?.Param || []),
}))
: []
}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={as_config?.buff_id?.toString()} selectedCustomSet={as_config?.buff_id?.toString()}
placeholder={transI18n("selectBuff")} placeholder={transI18n("selectBuff")}
@@ -224,16 +211,19 @@ export default function AsBar() {
{/* Turbulence Buff */} {/* Turbulence Buff */}
<div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20"> <div className="bg-base-200/20 rounded-lg p-4 border border-purple-500/20">
<h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2> <h2 className="text-2xl font-bold mb-2 text-info">{transI18n("turbulenceBuff")}</h2>
{eventSelected && eventSelected.Buff?.Name ? ( {challengeSelected ? (
<div challengeSelected.MazeBuff.map((buff, i) => (
className="text-base" <div
dangerouslySetInnerHTML={{ key={i}
__html: replaceByParam( className="text-base"
eventSelected.Buff?.Desc || "", dangerouslySetInnerHTML={{
eventSelected.Buff?.Param || [] __html: replaceByParam(
) getLocaleName(locale, buff?.Desc) || "",
}} buff?.Param || []
/> )
}}
/>
))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
@@ -247,48 +237,85 @@ export default function AsBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
{challengeSelected && challengeSelected?.EventIDList1?.length > 0 && challengeSelected?.EventIDList1[0].MonsterList.map((wave, waveIndex) => ( {challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => ( {Object.values(wave).map((waveValue, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[waveValue.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" challengeSelected?.EventList1?.[0]?.EliteGroup,
> challengeSelected?.EventList1?.[0]?.HardLevelGroup,
<div className="flex items-center space-x-3"> challengeSelected?.EventList1?.[0]?.Level,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> hardLevelConfig,
{mapMonster?.[waveValue.toString()]?.icon && <Image eliteConfig
unoptimized );
crossOrigin="anonymous" return (
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`} <div
alt="Enemy Icon" key={enemyIndex}
width={376} className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
height={512} >
className="w-full h-full object-cover" <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
/>} Lv. {challengeSelected?.EventList1[0].Level}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-3 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}
@@ -298,49 +325,85 @@ export default function AsBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
{challengeSelected && challengeSelected?.EventIDList2?.length > 0 && challengeSelected?.EventIDList2[0].MonsterList.map((wave, waveIndex) => ( {challengeSelected && challengeSelected?.EventList2?.length > 0 && challengeSelected?.EventList2?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => ( {Object.values(wave).map((waveValue, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[waveValue.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" challengeSelected?.EventList2?.[0]?.EliteGroup,
> challengeSelected?.EventList2?.[0]?.HardLevelGroup,
challengeSelected?.EventList2?.[0]?.Level,
<div className="flex items-center space-x-3"> hardLevelConfig,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> eliteConfig
{mapMonster?.[waveValue.toString()]?.icon && <Image );
unoptimized return (
crossOrigin="anonymous" <div
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`} key={enemyIndex}
alt="Enemy Icon" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
width={376} >
height={512} <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
className="w-full h-full object-cover" Lv. {challengeSelected?.EventList2[0].Level}
/>}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[waveValue.toString()].weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}

View File

@@ -9,23 +9,21 @@ import {
CopyPlus, CopyPlus,
} from "lucide-react"; } from "lucide-react";
import useMazeStore from "@/stores/mazeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useMonsterStore from "@/stores/monsterStore";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import { getLocaleName } from "@/helper"; import { getLocaleName } from "@/helper";
import Image from "next/image"; import Image from "next/image";
import { MonsterBasic } from "@/types";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useGlobalStore from "@/stores/globalStore"; import useGlobalStore from "@/stores/globalStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { MonsterDetail } from "@/types";
export default function CeBar() { export default function CeBar() {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null); const [showSearchWaveId, setShowSearchWaveId] = useState<number | null>(null);
const { Stage } = useMazeStore()
const { ce_config, setCeConfig } = useUserDataStore() const { ce_config, setCeConfig } = useUserDataStore()
const { listMonster, mapMonster } = useMonsterStore() const { mapMonster, stage, damageType } = useDetailDataStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const [showSearchStage, setShowSearchStage] = useState(false) const [showSearchStage, setShowSearchStage] = useState(false)
@@ -39,17 +37,17 @@ export default function CeBar() {
const [monsterPage, setMonsterPage] = useState(1) const [monsterPage, setMonsterPage] = useState(1)
const filteredMonsters = useMemo(() => { const filteredMonsters = useMemo(() => {
const newlistMonster = new Set<MonsterBasic>() const newlistMonster = new Set<MonsterDetail>()
for (const monster of listMonster) { for (const monster of Object.values(mapMonster)) {
if (getLocaleName(locale, monster).toLowerCase().includes(searchTerm.toLowerCase())) { if (getLocaleName(locale, monster.Name).toLowerCase().includes(searchTerm.toLowerCase())) {
newlistMonster.add(monster) newlistMonster.add(monster)
} }
if (monster.id.toLowerCase().includes(searchTerm.toLowerCase())) { if (monster.ID.toString().includes(searchTerm.toLowerCase())) {
newlistMonster.add(monster) newlistMonster.add(monster)
} }
} }
return Array.from(newlistMonster) return Array.from(newlistMonster)
}, [listMonster, locale, searchTerm]); }, [locale, searchTerm, mapMonster]);
const paginatedMonsters = useMemo(() => const paginatedMonsters = useMemo(() =>
filteredMonsters.slice((monsterPage - 1) * pageSizeMonsters, monsterPage * pageSizeMonsters), filteredMonsters.slice((monsterPage - 1) * pageSizeMonsters, monsterPage * pageSizeMonsters),
@@ -65,10 +63,10 @@ export default function CeBar() {
setMonsterPage(1) setMonsterPage(1)
}, [searchTerm]) }, [searchTerm])
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({ const stageList = useMemo(() => Object.values(stage).map((item) => ({
id: stage.stage_id.toString(), id: item.ID.toString(),
name: `${stage.stage_type} (${stage.stage_id})`, name: `${getLocaleName(locale, item.Name)} (${item.ID})`,
})), [Stage]) })), [stage, locale])
const filteredStages = useMemo(() => stageList.filter((s) => const filteredStages = useMemo(() => stageList.filter((s) =>
s.name.toLowerCase().includes(stageSearchTerm.toLowerCase()) s.name.toLowerCase().includes(stageSearchTerm.toLowerCase())
@@ -312,10 +310,10 @@ export default function CeBar() {
</button> </button>
<div className="flex justify-center"> <div className="flex justify-center">
{mapMonster?.[member.monster_id.toString()]?.icon && <Image {mapMonster?.[member.monster_id.toString()]?.Image?.IconPath && <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[member.monster_id.toString()]?.icon}`} src={`${process.env.CDN_URL}/${mapMonster?.[member.monster_id.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={376} width={376}
height={512} height={512}
@@ -324,11 +322,11 @@ export default function CeBar() {
</div> </div>
<div className="flex flex-wrap justify-center gap-1 mb-2"> <div className="flex flex-wrap justify-center gap-1 mb-2">
{mapMonster?.[member.monster_id.toString()]?.weak?.map((icon, iconIndex) => ( {mapMonster?.[member.monster_id.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon} alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200} width={200}
@@ -339,7 +337,7 @@ export default function CeBar() {
</div> </div>
<div className="text-center flex flex-col items-center justify-center"> <div className="text-center flex flex-col items-center justify-center">
<div className="text-sm font-medium"> <div className="text-sm font-medium">
{getLocaleName(locale, mapMonster?.[member.monster_id.toString()])} {`(${member.monster_id})`} {getLocaleName(locale, mapMonster?.[member.monster_id.toString()]?.Name)} {`(${member.monster_id})`}
</div> </div>
<div className="flex items-center gap-1 mt-1 mx-2"> <div className="flex items-center gap-1 mt-1 mx-2">
<span className="text-sm">LV.</span> <span className="text-sm">LV.</span>
@@ -444,12 +442,12 @@ export default function CeBar() {
{paginatedMonsters.length > 0 ? ( {paginatedMonsters.length > 0 ? (
paginatedMonsters.map((monster) => ( paginatedMonsters.map((monster) => (
<div <div
key={monster.id} key={monster.ID}
className="flex items-center gap-2 p-2 hover:bg-success/40 rounded cursor-pointer" className="flex items-center gap-2 p-2 hover:bg-success/40 rounded cursor-pointer"
onClick={() => { onClick={() => {
const newCeConfig = structuredClone(ce_config) const newCeConfig = structuredClone(ce_config)
newCeConfig.monsters[waveIndex].push({ newCeConfig.monsters[waveIndex].push({
monster_id: Number(monster.id), monster_id: monster.ID,
level: 95, level: 95,
amount: 1, amount: 1,
}) })
@@ -458,11 +456,11 @@ export default function CeBar() {
}} }}
> >
<div className="relative w-8 h-8 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> <div className="relative w-8 h-8 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{mapMonster?.[monster.id.toString()]?.icon && ( {mapMonster?.[monster.ID.toString()]?.Image?.IconPath && (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[monster.id.toString()]?.icon}`} src={`${process.env.CDN_URL}/${mapMonster?.[monster.ID.toString()]?.Image?.IconPath}`}
alt="Enemy Icon" alt="Enemy Icon"
width={376} width={376}
height={512} height={512}
@@ -470,7 +468,7 @@ export default function CeBar() {
/> />
)} )}
</div> </div>
<span>{getLocaleName(locale, monster)} {`(${monster.id})`}</span> <span>{getLocaleName(locale, monster.Name)} {`(${monster.ID})`}</span>
</div> </div>
)) ))
) : ( ) : (

View File

@@ -2,115 +2,117 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import useEventStore from "@/stores/eventStore"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import { getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useMonsterStore from "@/stores/monsterStore";
import Image from "next/image"; import Image from "next/image";
import useMazeStore from "@/stores/mazeStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types"; import { MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore";
export default function MocBar() { export default function MocBar() {
const { MOCEvent, mapMOCInfo } = useEventStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
moc_config, moc_config,
setMocConfig setMocConfig
} = useUserDataStore() } = useUserDataStore()
const { MOC } = useMazeStore() const { mapMonster, mapMoc, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id) return mapMoc[moc_config.event_id.toString()]?.Level.find((moc) => moc.ID === moc_config.challenge_id)
}, [moc_config, mapMOCInfo]) }, [moc_config, mapMoc])
const eventSelected = useMemo(() => {
return mapMoc[moc_config.event_id.toString()]
}, [moc_config, mapMoc])
useEffect(() => { useEffect(() => {
const challenge = mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id) if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
if (moc_config.event_id !== 0 && moc_config.challenge_id !== 0 && challenge) {
const newBattleConfig = structuredClone(moc_config) const newBattleConfig = structuredClone(moc_config)
newBattleConfig.cycle_count = 0 newBattleConfig.cycle_count = 0
if (moc_config.use_cycle_count) { if (moc_config.use_cycle_count) {
newBattleConfig.cycle_count = challenge.Countdown newBattleConfig.cycle_count = challengeSelected.TurnLimit
} }
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (moc_config.use_turbulence_buff && MOC[moc_config.challenge_id.toString()]) { if (moc_config.use_turbulence_buff && challengeSelected) {
challengeSelected.MazeBuff.map((item) => {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: Number(MOC[moc_config.challenge_id.toString()].maze_buff), id: item.ID,
level: 1 level: 1
}) })
} })
newBattleConfig.monsters = []
newBattleConfig.stage_id = 0
if ((moc_config.floor_side === "Upper" || moc_config.floor_side === "Upper -> Lower") && challenge.EventIDList1.length > 0) {
newBattleConfig.stage_id = challenge.EventIDList1[0].StageID
for (const wave of challenge.EventIDList1[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: Number(value),
level: challenge.EventIDList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if ((moc_config.floor_side === "Lower" || moc_config.floor_side === "Lower -> Upper") && challenge.EventIDList2.length > 0) {
newBattleConfig.stage_id = challenge.EventIDList2[0].StageID
for (const wave of challenge.EventIDList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: Number(value),
level: challenge.EventIDList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if (moc_config.floor_side === "Lower -> Upper" && challenge.EventIDList1.length > 0) {
for (const wave of challenge.EventIDList1[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: Number(value),
level: challenge.EventIDList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
} else if (moc_config.floor_side === "Upper -> Lower" && challenge.EventIDList2.length > 0) {
for (const wave of challenge.EventIDList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: Number(value),
level: challenge.EventIDList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
setMocConfig(newBattleConfig)
} }
newBattleConfig.monsters = []
newBattleConfig.stage_id = 0
if ((moc_config.floor_side === "Upper" || moc_config.floor_side === "Upper -> Lower") && challengeSelected.EventList1.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if ((moc_config.floor_side === "Lower" || moc_config.floor_side === "Lower -> Upper") && challengeSelected.EventList2.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
if (moc_config.floor_side === "Lower -> Upper" && challengeSelected.EventList1.length > 0) {
for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList1[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
} else if (moc_config.floor_side === "Upper -> Lower" && challengeSelected.EventList2.length > 0) {
for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) {
newWave.push({
monster_id: value,
level: challengeSelected.EventList2[0].Level,
amount: 1,
})
}
newBattleConfig.monsters.push(newWave)
}
}
setMocConfig(newBattleConfig)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
moc_config.event_id, moc_config.event_id,
moc_config.challenge_id, moc_config.challenge_id,
moc_config.floor_side, moc_config.floor_side,
mapMOCInfo,
MOC,
moc_config.use_cycle_count, moc_config.use_cycle_count,
moc_config.use_turbulence_buff, moc_config.use_turbulence_buff,
mapMoc,
]) ])
if (!MOCEvent) return null
if (!mapMoc) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
@@ -118,10 +120,10 @@ export default function MocBar() {
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={MOCEvent.map((moc) => ({ customSet={Object.values(mapMoc).sort((a, b) => b.ID - a.ID).map((moc) => ({
id: moc.id, id: moc.ID.toString(),
name: getLocaleName(locale, moc), name: getLocaleName(locale, moc.Name),
time: `${moc.begin} - ${moc.end}`, time: `${moc.BeginTime} - ${moc.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={moc_config.event_id.toString()} selectedCustomSet={moc_config.event_id.toString()}
@@ -129,7 +131,7 @@ export default function MocBar() {
setSelectedCustomSet={(id) => setMocConfig({ setSelectedCustomSet={(id) => setMocConfig({
...moc_config, ...moc_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapMOCInfo[Number(id)]?.slice(-1)[0]?.Id || 0, challenge_id: mapMoc[Number(id)]?.Level.at(-1)?.ID || 0,
})} })}
/> />
</div> </div>
@@ -149,8 +151,8 @@ export default function MocBar() {
})} })}
> >
<option value={0} disabled={true}>Select a Floor</option> <option value={0} disabled={true}>Select a Floor</option>
{mapMOCInfo[moc_config.event_id.toString()]?.map((moc) => ( {eventSelected?.Level?.map((moc) => (
<option key={moc.Id} value={moc.Id}>{moc.Id % 100}</option> <option key={moc.ID} value={moc.ID}>{getLocaleName(locale, moc.Name)}</option>
))} ))}
</select> </select>
</div> </div>
@@ -205,16 +207,19 @@ export default function MocBar() {
{transI18n("useTurbulenceBuff")} {transI18n("useTurbulenceBuff")}
</span> </span>
</div> </div>
{challengeSelected && challengeSelected?.Desc ? ( {challengeSelected ? (
<div challengeSelected.MazeBuff.map((buff, i) => (
className="text-base" <div
dangerouslySetInnerHTML={{ key={i}
__html: replaceByParam( className="text-base"
challengeSelected?.Desc || "", dangerouslySetInnerHTML={{
challengeSelected?.Param || [] __html: replaceByParam(
) getLocaleName(locale, buff?.Desc) || "",
}} buff?.Param || []
/> )
}}
/>
))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
@@ -228,48 +233,85 @@ export default function MocBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
{challengeSelected && challengeSelected?.EventIDList1?.length > 0 && challengeSelected?.EventIDList1[0].MonsterList.map((wave, waveIndex) => ( {challengeSelected && challengeSelected?.EventList1?.length > 0 && challengeSelected?.EventList1?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => ( {Object.values(wave).map((waveValue, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[waveValue.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" challengeSelected?.EventList1?.[0]?.EliteGroup,
> challengeSelected?.EventList1?.[0]?.HardLevelGroup,
<div className="flex items-center space-x-3"> challengeSelected?.EventList1?.[0]?.Level,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> hardLevelConfig,
{mapMonster?.[waveValue.toString()]?.icon && <Image eliteConfig
unoptimized );
crossOrigin="anonymous" return (
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`} <div
alt="Enemy Icon" key={enemyIndex}
width={376} className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
height={512} >
className="w-full h-full object-cover" <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
/>} Lv. {challengeSelected?.EventList1[0].Level}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}
@@ -279,49 +321,85 @@ export default function MocBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
{challengeSelected && challengeSelected?.EventIDList2?.length > 0 && challengeSelected?.EventIDList2[0].MonsterList.map((wave, waveIndex) => ( {challengeSelected && challengeSelected?.EventList2?.length > 0 && challengeSelected?.EventList2?.[0]?.MonsterList?.map((wave, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => ( {Object.values(wave).map((waveValue, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[waveValue.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" challengeSelected?.EventList2?.[0]?.EliteGroup,
> challengeSelected?.EventList2?.[0]?.HardLevelGroup,
challengeSelected?.EventList2?.[0]?.Level,
<div className="flex items-center space-x-3"> hardLevelConfig,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> eliteConfig
{mapMonster?.[waveValue.toString()]?.icon && <Image );
unoptimized return (
crossOrigin="anonymous" <div
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`} key={enemyIndex}
alt="Enemy Icon" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
width={376} >
height={512} <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
className="w-full h-full object-cover" Lv. {challengeSelected?.EventList2[0].Level}
/>}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[waveValue.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}

View File

@@ -1,57 +1,49 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import useEventStore from "@/stores/eventStore"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import { getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";;
import useMonsterStore from "@/stores/monsterStore";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types"; import { MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore";
export default function PeakBar() { export default function PeakBar() {
const { PEAKEvent, mapPEAKInfo } = useEventStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
peak_config, peak_config,
setPeakConfig setPeakConfig
} = useUserDataStore() } = useUserDataStore()
const { mapMonster, mapPeak, damageType, eliteConfig, hardLevelConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const listFloor = useMemo(() => { const listFloor = useMemo(() => {
if (!mapPEAKInfo?.[peak_config?.event_id?.toString()]) return [] const peak = mapPeak?.[peak_config?.event_id?.toString()]
return [ if (!peak) return []
...mapPEAKInfo[peak_config?.event_id?.toString()]?.PreLevel,
mapPEAKInfo[peak_config?.event_id?.toString()]?.BossLevel,
]
}, [peak_config, mapPEAKInfo])
return [...peak.PreLevel, peak.BossLevel].filter(it => it != null)
}, [peak_config, mapPeak])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapPEAKInfo?.[peak_config?.event_id?.toString()] return mapPeak?.[peak_config?.event_id?.toString()]
}, [peak_config, mapPEAKInfo]) }, [peak_config, mapPeak])
const bossConfig = useMemo(() => { const bossConfig = useMemo(() => {
return mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossConfig; return mapPeak?.[peak_config?.event_id?.toString()]?.BossConfig;
}, [peak_config, mapPEAKInfo]) }, [peak_config, mapPeak])
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
const challenge = structuredClone(listFloor.find((peak) => peak.Id === peak_config.challenge_id)) const challenge = structuredClone(listFloor?.find((peak) => peak?.ID === peak_config.challenge_id))
if ( if (
challenge challenge
&& challenge.Id === mapPEAKInfo?.[peak_config?.event_id?.toString()]?.BossLevel?.Id && challenge.ID === mapPeak?.[peak_config?.event_id?.toString()]?.BossLevel?.ID
&& bossConfig && bossConfig
&& peak_config?.boss_mode === "Hard" && peak_config?.boss_mode === "Hard"
) { ) {
challenge.Name = bossConfig.HardName return bossConfig
challenge.EventIDList = bossConfig.EventIDList
challenge.InfiniteList = bossConfig.InfiniteList
challenge.TagList = bossConfig.TagList
} }
return challenge return challenge
}, [peak_config, listFloor, mapPEAKInfo, bossConfig]) }, [peak_config, listFloor, mapPeak, bossConfig])
useEffect(() => { useEffect(() => {
if (!challengeSelected) return if (!challengeSelected) return
@@ -59,9 +51,9 @@ export default function PeakBar() {
const newBattleConfig = structuredClone(peak_config) const newBattleConfig = structuredClone(peak_config)
newBattleConfig.cycle_count = 6 newBattleConfig.cycle_count = 6
newBattleConfig.blessings = [] newBattleConfig.blessings = []
for (const value of challengeSelected.TagList) { for (const value of challengeSelected.MazeBuff) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
id: Number(value.Id), id: value.ID,
level: 1 level: 1
}) })
} }
@@ -72,15 +64,15 @@ export default function PeakBar() {
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = challengeSelected.EventIDList[0].StageID newBattleConfig.stage_id = challengeSelected.EventList[0].ID
for (const wave of challengeSelected.EventIDList[0].MonsterList) { for (const wave of challengeSelected.EventList[0].MonsterList) {
if (!wave) continue if (!wave) continue
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
if (!value) continue if (!value) continue
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList[0].Level, level: challengeSelected.EventList[0].Level,
amount: 1, amount: 1,
}) })
} }
@@ -94,10 +86,11 @@ export default function PeakBar() {
peak_config.event_id, peak_config.event_id,
peak_config.challenge_id, peak_config.challenge_id,
peak_config.buff_id, peak_config.buff_id,
mapPEAKInfo, peak_config.boss_mode,
mapPeak,
]) ])
if (!PEAKEvent) return null if (!mapPeak) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
@@ -106,9 +99,9 @@ export default function PeakBar() {
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={PEAKEvent.map((peak) => ({ customSet={Object.values(mapPeak).sort((a, b) => b.ID - a.ID).map((peak) => ({
id: peak.id, id: peak.ID.toString(),
name: `${getLocaleName(locale, peak)} (${peak.id})`, name: `${getLocaleName(locale, peak.Name)} (${peak.ID})`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={peak_config.event_id.toString()} selectedCustomSet={peak_config.event_id.toString()}
@@ -119,7 +112,7 @@ export default function PeakBar() {
{/* Settings */} {/* Settings */}
<div className={ <div className={
`grid grid-cols-1 `grid grid-cols-1
${eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id ? "md:grid-cols-2" : ""} ${eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id ? "md:grid-cols-2" : ""}
gap-4 mb-4 justify-items-center items-center w-full`} gap-4 mb-4 justify-items-center items-center w-full`}
> >
@@ -134,11 +127,11 @@ export default function PeakBar() {
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{listFloor.map((peak) => ( {listFloor.map((peak) => (
<option key={peak.Id} value={peak.Id}>{peak.Name}</option> <option key={peak.ID} value={peak.ID}>{getLocaleName(locale, peak.Name)}</option>
))} ))}
</select> </select>
</div> </div>
{eventSelected && eventSelected.BossLevel.Id === peak_config.challenge_id && ( {eventSelected && eventSelected.BossLevel?.ID === peak_config.challenge_id && (
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span> <span className="label-text font-bold text-success">{transI18n("mode")}:{" "}</span>
@@ -158,20 +151,17 @@ export default function PeakBar() {
</div> </div>
{ {
eventSelected eventSelected
&& eventSelected.BossLevel.Id === peak_config.challenge_id && eventSelected.BossLevel?.ID === peak_config.challenge_id
&& bossConfig && bossConfig
&& bossConfig.BuffList
&& ( && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={ customSet={
Array.isArray(bossConfig.BuffList) bossConfig.BuffList.map((buff) => ({
? bossConfig.BuffList.map((buff) => ({ id: buff.ID.toString(),
id: buff.Id.toString(), name: getLocaleName(locale, buff?.Name || ""),
name: buff?.Name || "", description: replaceByParam(getLocaleName(locale, buff?.Desc || ""), buff?.Param || []),
description: replaceByParam(buff?.Desc || "", buff?.Param || []), }))
}))
: []
} }
excludeSet={[]} excludeSet={[]}
selectedCustomSet={peak_config?.buff_id?.toString()} selectedCustomSet={peak_config?.buff_id?.toString()}
@@ -189,19 +179,19 @@ export default function PeakBar() {
{transI18n("turbulenceBuff")} {transI18n("turbulenceBuff")}
</h2> </h2>
{challengeSelected && challengeSelected?.TagList?.length > 0 ? ( {challengeSelected && challengeSelected?.MazeBuff?.length > 0 ? (
challengeSelected.TagList.map((subOption, index) => ( challengeSelected.MazeBuff.map((subOption, index) => (
<div key={index}> <div key={index}>
<label className="label"> <label className="label">
<span className="label-text font-bold text-success"> <span className="label-text font-bold text-success">
{index + 1}. {subOption.Name} {index + 1}. {getLocaleName(locale, subOption.Name)}
</span> </span>
</label> </label>
<div <div
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
subOption.Desc, getLocaleName(locale, subOption.Desc),
subOption.Param || [] subOption.Param || []
) )
}} }}
@@ -220,51 +210,87 @@ export default function PeakBar() {
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-4">
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{challengeSelected?.Name}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{getLocaleName(locale, challengeSelected?.Name)}</h2>
{challengeSelected && Object.values(challengeSelected.InfiniteList).map((waveValue, waveIndex) => ( {challengeSelected && Object.values(challengeSelected?.EventList?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => ( {Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[monsterId.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" waveValue.EliteGroup,
> challengeSelected?.EventList?.[0]?.HardLevelGroup,
challengeSelected?.EventList?.[0]?.Level,
<div className="flex items-center space-x-3"> hardLevelConfig,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> eliteConfig
{mapMonster?.[monsterId.toString()]?.icon && <Image );
unoptimized return (
crossOrigin="anonymous" <div
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`} key={enemyIndex}
alt="Enemy Icon" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
width={376} >
height={512} <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
className="w-full h-full object-cover" Lv. {challengeSelected?.EventList[0].Level}
/>}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList[0].Level}</div> {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}

View File

@@ -1,41 +1,38 @@
"use client" "use client"
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText"; import SelectCustomText from "../select/customSelectText";
import useEventStore from "@/stores/eventStore"; import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import { getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useMonsterStore from "@/stores/monsterStore";
import Image from "next/image"; import Image from "next/image";
import { MonsterStore } from "@/types"; import { MonsterStore } from "@/types";
import useMazeStore from "@/stores/mazeStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useDetailDataStore from "@/stores/detailDataStore";
export default function PfBar() { export default function PfBar() {
const { PFEvent, mapPFInfo } = useEventStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const { const {
pf_config, pf_config,
setPfConfig setPfConfig
} = useUserDataStore() } = useUserDataStore()
const { PF } = useMazeStore() const { mapMonster, mapPF, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => { const challengeSelected = useMemo(() => {
return mapPFInfo[pf_config.event_id.toString()]?.Level.find((pf) => pf.Id === pf_config.challenge_id) return mapPF[pf_config.event_id.toString()]?.Level.find((pf) => pf.ID === pf_config.challenge_id)
}, [pf_config, mapPFInfo]) }, [pf_config, mapPF])
const eventSelected = useMemo(() => { const eventSelected = useMemo(() => {
return mapPFInfo[pf_config.event_id.toString()] return mapPF[pf_config.event_id.toString()]
}, [pf_config, mapPFInfo]) }, [pf_config, mapPF])
useEffect(() => { useEffect(() => {
if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) { if (!challengeSelected || pf_config.event_id === 0 || pf_config.challenge_id === 0) {
return return
} }
const newBattleConfig = structuredClone(pf_config) const newBattleConfig = structuredClone(pf_config)
newBattleConfig.cycle_count = 4 newBattleConfig.cycle_count = challengeSelected.TurnLimit
newBattleConfig.blessings = [] newBattleConfig.blessings = []
if (pf_config.buff_id !== 0) { if (pf_config.buff_id !== 0) {
newBattleConfig.blessings.push({ newBattleConfig.blessings.push({
@@ -43,62 +40,63 @@ export default function PfBar() {
level: 1 level: 1
}) })
} }
if (PF[pf_config.challenge_id.toString()]) { if (challengeSelected) {
newBattleConfig.blessings.push({ challengeSelected.MazeBuff.map((item) => {
id: Number(PF[pf_config.challenge_id.toString()].maze_buff), newBattleConfig.blessings.push({
level: 1 id: item.ID,
level: 1
})
}) })
} }
newBattleConfig.monsters = [] newBattleConfig.monsters = []
newBattleConfig.stage_id = 0 newBattleConfig.stage_id = 0
if ((pf_config.floor_side === "Upper" || pf_config.floor_side === "Upper -> Lower") && challengeSelected.EventIDList1.length > 0) { if ((pf_config.floor_side === "Upper" || pf_config.floor_side === "Upper -> Lower") && challengeSelected.EventList1.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventIDList1[0].StageID newBattleConfig.stage_id = challengeSelected.EventList1[0].ID
for (const wave of challengeSelected.EventIDList1[0].MonsterList) { for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList1[0].Level, level: challengeSelected.EventList1[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
if ((pf_config.floor_side === "Lower" || pf_config.floor_side === "Lower -> Upper") && challengeSelected.EventIDList2.length > 0) { if ((pf_config.floor_side === "Lower" || pf_config.floor_side === "Lower -> Upper") && challengeSelected.EventList2.length > 0) {
newBattleConfig.stage_id = challengeSelected.EventIDList2[0].StageID newBattleConfig.stage_id = challengeSelected.EventList2[0].ID
for (const wave of challengeSelected.EventIDList2[0].MonsterList) { for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList2[0].Level, level: challengeSelected.EventList2[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} }
if (pf_config.floor_side === "Lower -> Upper" && challengeSelected.EventIDList1.length > 0) { if (pf_config.floor_side === "Lower -> Upper" && challengeSelected.EventList1.length > 0) {
for (const wave of challengeSelected.EventIDList1[0].MonsterList) { for (const wave of challengeSelected.EventList1[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList1[0].Level, level: challengeSelected.EventList1[0].Level,
amount: 1, amount: 1,
}) })
} }
newBattleConfig.monsters.push(newWave) newBattleConfig.monsters.push(newWave)
} }
} else if (pf_config.floor_side === "Upper -> Lower" && challengeSelected.EventIDList2.length > 0) { } else if (pf_config.floor_side === "Upper -> Lower" && challengeSelected.EventList2.length > 0) {
for (const wave of challengeSelected.EventIDList2[0].MonsterList) { for (const wave of challengeSelected.EventList2[0].MonsterList) {
const newWave: MonsterStore[] = [] const newWave: MonsterStore[] = []
for (const value of Object.values(wave)) { for (const value of Object.values(wave)) {
newWave.push({ newWave.push({
monster_id: Number(value), monster_id: value,
level: challengeSelected.EventIDList2[0].Level, level: challengeSelected.EventList2[0].Level,
amount: 1, amount: 1,
}) })
} }
@@ -113,10 +111,9 @@ export default function PfBar() {
pf_config.challenge_id, pf_config.challenge_id,
pf_config.floor_side, pf_config.floor_side,
pf_config.buff_id, pf_config.buff_id,
mapPFInfo, mapPF,
PF,
]) ])
if (!PFEvent) return null if (!mapPF) return null
return ( return (
<div className="py-8 relative"> <div className="py-8 relative">
@@ -125,10 +122,10 @@ export default function PfBar() {
<div className="rounded-xl p-4 mb-2 border border-warning"> <div className="rounded-xl p-4 mb-2 border border-warning">
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={PFEvent.map((pf) => ({ customSet={Object.values(mapPF).sort((a, b) => b.ID - a.ID).map((pf) => ({
id: pf.id, id: pf.ID.toString(),
name: getLocaleName(locale, pf), name: getLocaleName(locale, pf.Name),
time: `${pf.begin} - ${pf.end}`, time: `${pf.BeginTime} - ${pf.EndTime}`,
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={pf_config.event_id.toString()} selectedCustomSet={pf_config.event_id.toString()}
@@ -136,7 +133,7 @@ export default function PfBar() {
setSelectedCustomSet={(id) => setPfConfig({ setSelectedCustomSet={(id) => setPfConfig({
...pf_config, ...pf_config,
event_id: Number(id), event_id: Number(id),
challenge_id: mapPFInfo[Number(id)]?.Level.slice(-1)[0]?.Id || 0, challenge_id: mapPF[Number(id)]?.Level.slice(-1)[0]?.ID || 0,
buff_id: 0 buff_id: 0
})} })}
/> />
@@ -154,8 +151,8 @@ export default function PfBar() {
onChange={(e) => setPfConfig({ ...pf_config, challenge_id: Number(e.target.value) })} onChange={(e) => setPfConfig({ ...pf_config, challenge_id: Number(e.target.value) })}
> >
<option value={0} disabled={true}>{transI18n("selectFloor")}</option> <option value={0} disabled={true}>{transI18n("selectFloor")}</option>
{mapPFInfo[pf_config.event_id.toString()]?.Level.map((pf) => ( {eventSelected?.Level.map((pf) => (
<option key={pf.Id} value={pf.Id}>{pf.Id % 10}</option> <option key={pf.ID} value={pf.ID}>{getLocaleName(locale, pf.Name)}</option>
))} ))}
</select> </select>
</div> </div>
@@ -180,10 +177,10 @@ export default function PfBar() {
{eventSelected && ( {eventSelected && (
<div className="mb-4 w-full"> <div className="mb-4 w-full">
<SelectCustomText <SelectCustomText
customSet={eventSelected.Option.map((buff, index) => ({ customSet={eventSelected.Option.map((buff) => ({
id: PF[eventSelected.Id.toString()]?.buff[index]?.toString(), id: buff.ID?.toString() || "",
name: buff.Name, name: getLocaleName(locale, buff?.Name) || "",
description: replaceByParam(buff.Desc, buff.Param || []), description: replaceByParam(getLocaleName(locale, buff?.Desc) || "", buff?.Param || []),
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={pf_config?.buff_id?.toString()} selectedCustomSet={pf_config?.buff_id?.toString()}
@@ -199,29 +196,32 @@ export default function PfBar() {
eventSelected.SubOption.map((subOption, index) => ( eventSelected.SubOption.map((subOption, index) => (
<div key={index}> <div key={index}>
<label className="label"> <label className="label">
<span className="label-text font-bold text-success">{index + 1}. {subOption.Name}</span> <span className="label-text font-bold text-success">{index + 1}. {getLocaleName(locale, subOption.Name)}</span>
</label> </label>
<div <div
className="text-base" className="text-base"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
subOption.Desc, getLocaleName(locale, subOption.Desc) || "",
subOption.Param || [] subOption.Param || []
) )
}} }}
/> />
</div> </div>
)) ))
) : eventSelected && eventSelected.SubOption.length === 0 ? ( ) : eventSelected && challengeSelected && eventSelected.SubOption.length === 0 ? (
<div challengeSelected?.MazeBuff?.map((buff, i) => (
className="text-base" <div
dangerouslySetInnerHTML={{ key={i}
__html: replaceByParam( className="text-base"
eventSelected.Buff?.Desc || "", dangerouslySetInnerHTML={{
eventSelected.Buff?.Param || [] __html: replaceByParam(
) getLocaleName(locale, buff?.Desc) || "",
}} buff?.Param || []
/> )
}}
/>
))
) : ( ) : (
<div className="text-base">{transI18n("noTurbulenceBuff")}</div> <div className="text-base">{transI18n("noTurbulenceBuff")}</div>
)} )}
@@ -236,49 +236,85 @@ export default function PfBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("firstHalfEnemies")}</h2>
{challengeSelected && Object.values(challengeSelected.InfiniteList1).map((waveValue, waveIndex) => ( {challengeSelected && Object.values(challengeSelected.EventList1?.[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => ( {Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
const monsterStats = calcMonsterStats(
<div mapMonster?.[monsterId.toString()],
key={enemyIndex} waveValue.EliteGroup,
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" challengeSelected?.EventList1?.[0]?.HardLevelGroup,
> challengeSelected?.EventList1?.[0]?.Level,
<div className="flex items-center space-x-3"> hardLevelConfig,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> eliteConfig
{mapMonster?.[monsterId.toString()]?.icon && <Image );
unoptimized return (
crossOrigin="anonymous" <div
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`} key={enemyIndex}
alt="Enemy Icon" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
width={376} >
height={512} <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
className="w-full h-full object-cover" Lv. {challengeSelected?.EventList1[0].Level}
/>}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">{mapMonster?.[monsterId.toString()]?.id} | Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}
@@ -288,49 +324,85 @@ export default function PfBar() {
<div className="rounded-xl p-4 mt-2 border border-warning"> <div className="rounded-xl p-4 mt-2 border border-warning">
<h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2> <h2 className="text-2xl font-bold mb-6 text-info">{transI18n("secondHalfEnemies")}</h2>
{challengeSelected && Object.values(challengeSelected?.InfiniteList2).map((waveValue, waveIndex) => ( {challengeSelected && Object.values(challengeSelected?.EventList2[0]?.Infinite || []).map((waveValue, waveIndex) => (
<div key={waveIndex} className="mb-6"> <div key={waveIndex} className="mb-6">
<h3 className="text-lg font-semibold mb-t">{transI18n("wave")} {waveIndex + 1}</h3> <h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-2 mt-2">
{Array.from(new Set(waveValue.MonsterGroupIDList)).map((monsterId, enemyIndex) => ( {Array.from(new Set(waveValue.MonsterList)).map((monsterId, enemyIndex) => {
<div const monsterStats = calcMonsterStats(
key={enemyIndex} mapMonster?.[monsterId.toString()],
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition" waveValue.EliteGroup,
> challengeSelected?.EventList2?.[0]?.HardLevelGroup,
challengeSelected?.EventList2?.[0]?.Level,
<div className="flex items-center space-x-3"> hardLevelConfig,
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm"> eliteConfig
{mapMonster?.[monsterId.toString()]?.icon && <Image );
unoptimized return (
crossOrigin="anonymous" <div
src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.icon}`} key={enemyIndex}
alt="Enemy Icon" className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
width={400} >
height={300} <div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
className="w-full h-full object-cover" Lv. {challengeSelected?.EventList2[0].Level}
/>}
</div> </div>
<div className="flex flex-col"> <div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div> {mapMonster?.[monsterId.toString()]?.Image?.IconPath && (
<div className="flex items-center space-x-1 mt-1"> <div className="relative w-16 h-16 rounded-full border-2 border-base-300 shadow-md overflow-hidden group-hover:scale-110 transition-transform duration-300 bg-base-100">
{mapMonster?.[monsterId.toString()]?.weak?.map((icon, iconIndex) => (
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${mapMonster?.[monsterId.toString()]?.Image?.IconPath}`}
alt={icon} alt="Enemy Icon"
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm" width={150}
width={200} height={150}
height={200} className="w-full h-full object-cover"
key={iconIndex}
/> />
))} </div>
)}
</div>
<div className="flex flex-col px-1 pb-2 pt-2">
<div className="flex flex-col space-y-1.5">
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-error">HP</span>
<span className="text-sm font-bold text-base-content">{monsterStats.hp.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-info">Speed</span>
<span className="text-sm font-bold text-base-content">{monsterStats.spd.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
<div className="flex justify-between items-center bg-base-200 px-2.5 py-1.5 rounded-lg">
<span className="text-xs font-semibold text-base-content/70">Toughness</span>
<span className="text-sm font-bold text-base-content">{monsterStats.stance.toLocaleString(undefined, { maximumFractionDigits: 0 })}</span>
</div>
</div>
<div className="mt-2 pt-2 border-t border-base-300 flex flex-col items-center">
<span className="text-[10px] text-base-content/60 font-bold uppercase tracking-widest mb-1.5">
Weakness
</span>
<div className="flex items-center justify-center gap-1.5 flex-wrap">
{mapMonster?.[monsterId.toString()]?.StanceWeakList?.map((icon, iconIndex) => (
<Image
key={iconIndex}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${damageType[icon]?.Icon}`}
alt={icon}
width={40}
height={40}
className="h-6 w-6 object-contain rounded-full bg-base-300 border border-base-content/10 p-0.5 shadow-sm hover:scale-110 transition-transform"
/>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>
))} ))}

View File

@@ -1,39 +1,34 @@
"use client" "use client"
import NextImage from "next/image" import NextImage from "next/image"
import useLightconeStore from "@/stores/lightconeStore";
import useAffixStore from "@/stores/affixStore";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useRelicStore from "@/stores/relicStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useMemo } from "react"; import { useMemo } from "react";
import useAvatarStore from "@/stores/avatarStore"; import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, getLocaleName, replaceByParam } from "@/helper";
import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, replaceByParam } from "@/helper";
import { mappingStats } from "@/constant/constant"; import { mappingStats } from "@/constant/constant";
import RelicShowcase from "../showcaseCard/relicShowcase"; import RelicShowcase from "../showcaseCard/relicShowcase";
import useLocaleStore from "@/stores/localeStore";
import useDetailDataStore from "@/stores/detailDataStore";
import useCurrentDataStore from "@/stores/currentDataStore";
export default function QuickView() { export default function QuickView() {
const { avatarSelected, mapAvatarInfo } = useAvatarStore()
const { mapLightconeInfo } = useLightconeStore()
const { mapMainAffix, mapSubAffix } = useAffixStore()
const { avatars } = useUserDataStore() const { avatars } = useUserDataStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { mapRelicInfo } = useRelicStore() const { locale } = useLocaleStore()
const { avatarSelected, } = useCurrentDataStore()
const avatarInfo = useMemo(() => { const { mainAffix, subAffix, mapRelicSet, mapLightCone, mapAvatar } = useDetailDataStore()
if (!avatarSelected) return
return mapAvatarInfo[avatarSelected.id]
}, [avatarSelected, mapAvatarInfo])
const avatarSkillTree = useMemo(() => { const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected.id]) return {} if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected.id].enhanced) { if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {} return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
} }
return avatarInfo?.SkillTrees || {} return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatarInfo, avatars]) }, [avatarSelected, avatars])
const avatarData = useMemo(() => { const avatarData = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return avatars[avatarSelected.id] return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarProfile = useMemo(() => { const avatarProfile = useMemo(() => {
@@ -42,7 +37,7 @@ export default function QuickView() {
}, [avatarSelected, avatarData]) }, [avatarSelected, avatarData])
const relicEffects = useMemo(() => { const relicEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID?.toString() || ""];
const relicCount: { [key: string]: number } = {}; const relicCount: { [key: string]: number } = {};
if (avatar) { if (avatar) {
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) { for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
@@ -63,32 +58,32 @@ export default function QuickView() {
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const relicStats = useMemo(() => { const relicStats = useMemo(() => {
if (!avatarSelected || !avatarProfile?.relics || !mapMainAffix || !mapSubAffix) return if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return
return Object.entries(avatarProfile?.relics).map(([key, value]) => { return Object.entries(avatarProfile?.relics).map(([key, value]) => {
const mainAffixMap = mapMainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = mapSubAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
return { return {
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`, img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
mainAffix: { mainAffix: {
property: mainAffixMap?.[value?.main_affix_id]?.property, property: mainAffixMap?.[value?.main_affix_id]?.Property,
level: value?.level, level: value?.level,
valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level), valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level),
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.property] detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property]
}, },
subAffix: value?.sub_affixes?.map((subValue) => { subAffix: value?.sub_affixes?.map((subValue) => {
return { return {
property: subAffixMap?.[subValue?.sub_affix_id]?.property, property: subAffixMap?.[subValue?.sub_affix_id]?.Property,
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count), valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property], detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property],
step: subValue?.step, step: subValue?.step,
count: subValue?.count count: subValue?.count
} }
}) })
} }
}) })
}, [avatarSelected, avatarProfile, mapMainAffix, mapSubAffix]) }, [avatarSelected, avatarProfile, mainAffix, subAffix])
const characterStats = useMemo(() => { const characterStats = useMemo(() => {
if (!avatarSelected || !avatarData) return if (!avatarSelected || !avatarData) return
@@ -104,73 +99,73 @@ export default function QuickView() {
}> = { }> = {
HP: { HP: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
name: "HP", name: "HP",
icon: "/icon/hp.webp", icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "", unit: "",
round: 0 round: 0
}, },
ATK: { ATK: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
name: "ATK", name: "ATK",
icon: "/icon/attack.webp", icon: "spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "", unit: "",
round: 0 round: 0
}, },
DEF: { DEF: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
name: "DEF", name: "DEF",
icon: "/icon/defence.webp", icon: "spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "", unit: "",
round: 0 round: 0
}, },
SPD: { SPD: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
name: "SPD", name: "SPD",
icon: "/icon/speed.webp", icon: "spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
round: 1 round: 1
}, },
CRITRate: { CRITRate: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
name: "CRIT Rate", name: "CRIT Rate",
icon: "/icon/crit-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
CRITDmg: { CRITDmg: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
name: "CRIT DMG", name: "CRIT DMG",
icon: "/icon/crit-damage.webp", icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -178,7 +173,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Break Effect", name: "Break Effect",
icon: "/icon/break-effect.webp", icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -186,7 +181,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect RES", name: "Effect RES",
icon: "/icon/effect-res.webp", icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -194,7 +189,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Energy Rate", name: "Energy Rate",
icon: "/icon/energy-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -202,7 +197,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect Hit Rate", name: "Effect Hit Rate",
icon: "/icon/effect-hit-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -210,7 +205,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Healing Boost", name: "Healing Boost",
icon: "/icon/healing-boost.webp", icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -218,7 +213,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Physical Boost", name: "Physical Boost",
icon: "/icon/physical-add.webp", icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -226,7 +221,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Fire Boost", name: "Fire Boost",
icon: "/icon/fire-add.webp", icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -234,7 +229,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Ice Boost", name: "Ice Boost",
icon: "/icon/ice-add.webp", icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -242,7 +237,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Thunder Boost", name: "Thunder Boost",
icon: "/icon/thunder-add.webp", icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -250,7 +245,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Wind Boost", name: "Wind Boost",
icon: "/icon/wind-add.webp", icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -258,7 +253,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Quantum Boost", name: "Quantum Boost",
icon: "/icon/quantum-add.webp", icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -266,7 +261,7 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Imaginary Boost", name: "Imaginary Boost",
icon: "/icon/imaginary-add.webp", icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -274,57 +269,57 @@ export default function QuickView() {
value: 0, value: 0,
base: 0, base: 0,
name: "Elation Boost", name: "Elation Boost",
icon: "/icon/IconJoy.webp", icon: "spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%", unit: "%",
round: 1 round: 1
} }
} }
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) { if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level) const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
statsData.HP.value += calcBaseStatRaw( statsData.HP.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.HP.base += calcBaseStatRaw( statsData.HP.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.value += calcBaseStatRaw( statsData.ATK.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.base += calcBaseStatRaw( statsData.ATK.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.value += calcBaseStatRaw( statsData.DEF.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.base += calcBaseStatRaw( statsData.DEF.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
const bonusData = mapLightconeInfo[avatarProfile?.lightcone?.item_id].Bonus?.[avatarProfile?.lightcone.rank - 1] const bonusData = mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus
if (bonusData && bonusData.length > 0) { if (bonusData && bonusData.length > 0) {
const bonusSpd = bonusData.filter((bonus) => bonus.type === "BaseSpeed") const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed")
const bonusOther = bonusData.filter((bonus) => bonus.type !== "BaseSpeed") const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed")
bonusSpd.forEach((bonus) => { bonusSpd.forEach((bonus) => {
statsData.SPD.value += bonus.value statsData.SPD.value += bonus.Value
statsData.SPD.base += bonus.value statsData.SPD.base += bonus.Value
}) })
bonusOther.forEach((bonus) => { bonusOther.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.type]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
} }
@@ -349,17 +344,17 @@ export default function QuickView() {
if (avatarProfile?.relics && mapMainAffix && mapSubAffix) { if (avatarProfile?.relics && mainAffix && subAffix) {
Object.entries(avatarProfile?.relics).forEach(([key, value]) => { Object.entries(avatarProfile?.relics).forEach(([key, value]) => {
const mainAffixMap = mapMainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = mapSubAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.property]?.baseStat const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat
if (mainStats && statsData[mainStats]) { if (mainStats && statsData[mainStats]) {
statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base) statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base)
} }
value?.sub_affixes.forEach((subValue) => { value?.sub_affixes.forEach((subValue) => {
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.property]?.baseStat const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat
if (subStats && statsData[subStats]) { if (subStats && statsData[subStats]) {
statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base) statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base)
} }
@@ -369,14 +364,14 @@ export default function QuickView() {
if (relicEffects && relicEffects.length > 0) { if (relicEffects && relicEffects.length > 0) {
relicEffects.forEach((relic) => { relicEffects.forEach((relic) => {
const dataBonus = mapRelicInfo?.[relic.key]?.Bonus const dataBonus = mapRelicSet?.[relic.key]?.Skills
if (!dataBonus || Object.keys(dataBonus).length === 0) return if (!dataBonus || Object.keys(dataBonus).length === 0) return
Object.entries(dataBonus || {}).forEach(([key, value]) => { Object.entries(dataBonus || {}).forEach(([key, value]) => {
if (relic.count < Number(key)) return if (relic.count < Number(key)) return
value.forEach((bonus) => { value.Bonus.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.type]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
}) })
@@ -388,14 +383,14 @@ export default function QuickView() {
}, [ }, [
avatarSelected, avatarSelected,
avatarData, avatarData,
mapAvatarInfo, mapAvatar,
avatarProfile?.lightcone, avatarProfile?.lightcone,
avatarProfile?.relics, avatarProfile?.relics,
mapLightconeInfo, mapLightCone,
mapMainAffix, mainAffix,
mapSubAffix, subAffix,
relicEffects, relicEffects,
mapRelicInfo, mapRelicSet,
avatarSkillTree avatarSkillTree
]) ])
@@ -409,13 +404,13 @@ export default function QuickView() {
<div key={index} className="flex flex-row items-center justify-between"> <div key={index} className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<NextImage <NextImage
src={stat?.icon || ""} src={stat?.icon || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt="Stat Icon" alt="Stat Icon"
width={40} width={40}
height={40} height={40}
className="h-auto w-10 p-1 mx-1 bg-black/20 rounded-full" className="h-10 w-10 p-1 mx-1 bg-black/20 rounded-full"
/> />
<div className="font-bold">{stat.name}</div> <div className="font-bold">{stat.name}</div>
</div> </div>
@@ -431,7 +426,7 @@ export default function QuickView() {
<div className="flex flex-col items-center gap-1 w-full my-2"> <div className="flex flex-col items-center gap-1 w-full my-2">
{relicEffects.map((setEffect, index) => { {relicEffects.map((setEffect, index) => {
const relicInfo = mapRelicInfo[setEffect.key]; const relicInfo = mapRelicSet[setEffect.key];
if (!relicInfo) return null; if (!relicInfo) return null;
return ( return (
<div key={index} className="flex w-full flex-row justify-between text-left"> <div key={index} className="flex w-full flex-row justify-between text-left">
@@ -442,7 +437,7 @@ export default function QuickView() {
}} }}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
relicInfo.Name, getLocaleName(locale, relicInfo.Name),
[] []
) )
}} }}
@@ -459,9 +454,9 @@ export default function QuickView() {
<div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg"> <div className="grid grid-cols-1 gap-2 justify-between py-3 text-lg">
{relicStats?.map((relic, index) => { {relicStats?.map((relic, index) => {
if (!relic || !avatarInfo) return null if (!relic || !avatarSelected) return null
return ( return (
<RelicShowcase key={index} relic={relic} avatarInfo={avatarInfo} /> <RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} />
) )
})} })}

View File

@@ -1,26 +1,27 @@
"use client"; "use client";
import useRelicStore from '@/stores/relicStore';
import useUserDataStore from '@/stores/userDataStore'; import useUserDataStore from '@/stores/userDataStore';
import { AffixDetail, RelicDetail } from '@/types'; import { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import SelectCustomImage from '../select/customSelectImage'; import SelectCustomImage from '../select/customSelectImage';
import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam } from '@/helper'; import { calcAffixBonus, calcMainAffixBonus, randomPartition, randomStep, replaceByParam, getLocaleName } from '@/helper';
import useAffixStore from '@/stores/affixStore';
import { mappingStats } from '@/constant/constant'; import { mappingStats } from '@/constant/constant';
import useAvatarStore from '@/stores/avatarStore';
import useModelStore from '@/stores/modelStore'; import useModelStore from '@/stores/modelStore';
import useRelicMakerStore from '@/stores/relicMakerStore'; import useRelicMakerStore from '@/stores/relicMakerStore';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import { ChevronDown, ChevronUp } from 'lucide-react'; import { ChevronDown, ChevronUp } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import useDetailDataStore from '@/stores/detailDataStore';
import useCurrentDataStore from '@/stores/currentDataStore';
import { RelicSetDetail, SubAffixData } from '@/types';
import useLocaleStore from '@/stores/localeStore';
import { mappingRelicSlot } from "@/constant/constant";
export default function RelicMaker() { export default function RelicMaker() {
const { avatars, setAvatars } = useUserDataStore() const { avatars, setAvatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore() const { avatarSelected } = useCurrentDataStore()
const { setIsOpenRelic } = useModelStore() const { setIsOpenRelic } = useModelStore()
const { mapRelicInfo } = useRelicStore() const { mainAffix, subAffix, mapRelicSet } = useDetailDataStore()
const { mapMainAffix, mapSubAffix } = useAffixStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { const {
selectedRelicSlot, selectedRelicSlot,
@@ -40,11 +41,11 @@ export default function RelicMaker() {
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const relicSets = useMemo(() => { const relicSets = useMemo(() => {
const listSet: Record<string, RelicDetail> = {}; const listSet: Record<string, RelicSetDetail> = {};
for (const [key, value] of Object.entries(mapRelicInfo || {})) { for (const [key, value] of Object.entries(mapRelicSet || {})) {
let isOk = false; let isOk = false;
for (const key2 of Object.keys(value.Parts)) { for (const key2 of Object.keys(value.Parts)) {
if (key2.endsWith(selectedRelicSlot)) { if (key2 == mappingRelicSlot?.[selectedRelicSlot]) {
isOk = true; isOk = true;
break; break;
} }
@@ -54,30 +55,30 @@ export default function RelicMaker() {
} }
} }
return listSet; return listSet;
}, [mapRelicInfo, selectedRelicSlot]); }, [mapRelicSet , selectedRelicSlot]);
const subAffixOptions = useMemo(() => { const subAffixOptions = useMemo(() => {
const listSet: Record<string, AffixDetail> = {}; const listSet: Record<string, SubAffixData> = {};
const subAffixMap = mapSubAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot] const mainAffixMap = mainAffix["5" + selectedRelicSlot]
if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet; if (Object.keys(subAffixMap || {}).length === 0 || Object.keys(mainAffixMap || {}).length === 0) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) { for (const [key, value] of Object.entries(subAffixMap)) {
if (value.property !== mainAffixMap[selectedMainStat]?.property) { if (value.Property !== mainAffixMap[selectedMainStat]?.Property) {
listSet[key] = value; listSet[key] = value;
} }
} }
return listSet; return listSet;
}, [mapSubAffix, mapMainAffix, selectedRelicSlot, selectedMainStat]); }, [subAffix, mainAffix, selectedRelicSlot, selectedMainStat]);
useEffect(() => { useEffect(() => {
const subAffixMap = mapSubAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot]; const mainAffixMap = mainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return; if (!subAffixMap || !mainAffixMap) return;
const mainProp = mainAffixMap[selectedMainStat]?.property; const mainProp = mainAffixMap[selectedMainStat]?.Property;
if (!mainProp) return; if (!mainProp) return;
const newSubAffixes = structuredClone(listSelectedSubStats); const newSubAffixes = structuredClone(listSelectedSubStats);
@@ -95,33 +96,33 @@ export default function RelicMaker() {
if (updated) setListSelectedSubStats(newSubAffixes); if (updated) setListSelectedSubStats(newSubAffixes);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedMainStat, mapSubAffix, mapMainAffix, selectedRelicSlot]); }, [selectedMainStat, subAffix, mainAffix, selectedRelicSlot]);
const exSubAffixOptions = useMemo(() => { const exSubAffixOptions = useMemo(() => {
const listSet: Record<string, AffixDetail> = {}; const listSet: Record<string, SubAffixData> = {};
const subAffixMap = mapSubAffix["5"]; const subAffixMap = subAffix["5"];
const mainAffixMap = mapMainAffix["5" + selectedRelicSlot]; const mainAffixMap = mainAffix["5" + selectedRelicSlot];
if (!subAffixMap || !mainAffixMap) return listSet; if (!subAffixMap || !mainAffixMap) return listSet;
for (const [key, value] of Object.entries(subAffixMap)) { for (const [key, value] of Object.entries(subAffixMap)) {
const subAffix = listSelectedSubStats.find((item) => item.property === value.property); const subAffix = listSelectedSubStats.find((item) => item.property === value.Property);
if (subAffix && value.property !== mainAffixMap[selectedMainStat]?.property) { if (subAffix && value.Property !== mainAffixMap[selectedMainStat]?.Property) {
listSet[key] = value; listSet[key] = value;
} }
} }
return listSet; return listSet;
}, [mapSubAffix, listSelectedSubStats, mapMainAffix, selectedRelicSlot, selectedMainStat]); }, [subAffix, listSelectedSubStats, mainAffix, selectedRelicSlot, selectedMainStat]);
const effectBonus = useMemo(() => { const effectBonus = useMemo(() => {
const affixSet = mapMainAffix?.["5" + selectedRelicSlot]; const affixSet = mainAffix?.["5" + selectedRelicSlot];
if (!affixSet) return 0; if (!affixSet) return 0;
const data = affixSet[selectedMainStat]; const data = affixSet[selectedMainStat];
if (!data) return 0; if (!data) return 0;
return calcMainAffixBonus(data, selectedRelicLevel); return calcMainAffixBonus(data, selectedRelicLevel);
}, [mapMainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]); }, [mainAffix, selectedRelicSlot, selectedMainStat, selectedRelicLevel]);
const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => { const handleSubStatChange = (key: string, index: number, rollCount: number, stepCount: number) => {
setError(""); setError("");
@@ -136,7 +137,7 @@ export default function RelicMaker() {
return; return;
} }
newSubAffixes[index].affixId = key; newSubAffixes[index].affixId = key;
newSubAffixes[index].property = subAffixOptions[key].property; newSubAffixes[index].property = subAffixOptions[key].Property;
newSubAffixes[index].rollCount = rollCount; newSubAffixes[index].rollCount = rollCount;
newSubAffixes[index].stepCount = stepCount; newSubAffixes[index].stepCount = stepCount;
setListSelectedSubStats(newSubAffixes); setListSelectedSubStats(newSubAffixes);
@@ -179,7 +180,7 @@ export default function RelicMaker() {
exKeys.push(randomKey); exKeys.push(randomKey);
const randomValue = subAffixOptions[randomKey]; const randomValue = subAffixOptions[randomKey];
newSubAffixes[i].affixId = randomKey; newSubAffixes[i].affixId = randomKey;
newSubAffixes[i].property = randomValue.property; newSubAffixes[i].property = randomValue.Property;
newSubAffixes[i].rollCount = 0; newSubAffixes[i].rollCount = 0;
newSubAffixes[i].stepCount = 0; newSubAffixes[i].stepCount = 0;
} }
@@ -205,7 +206,7 @@ export default function RelicMaker() {
const handlerSaveRelic = () => { const handlerSaveRelic = () => {
setError(""); setError("");
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID?.toString() || ""];
if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) { if (!selectedRelicSet || !selectedMainStat || !selectedRelicLevel || !selectedRelicSlot) {
setError(transI18n("pleaseSelectAllOptions")); setError(transI18n("pleaseSelectAllOptions"));
return; return;
@@ -258,10 +259,10 @@ export default function RelicMaker() {
<div> <div>
<label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label> <label className="block text-lg font-medium mb-2">{transI18n("mainStat")}</label>
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(mapMainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({ customSet={Object.entries(mainAffix["5" + selectedRelicSlot] || {}).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: mappingStats[value.property].icon imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={selectedMainStat} selectedCustomSet={selectedMainStat}
@@ -275,8 +276,8 @@ export default function RelicMaker() {
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(relicSets).map(([key, value]) => ({ customSet={Object.entries(relicSets).map(([key, value]) => ({
value: key, value: key,
label: value.Name, label: getLocaleName(locale, value.Name),
imageUrl: `${process.env.CDN_URL}/spriteoutput/itemfigures/${value.Icon.match(/\d+/)?.[0]}.png` imageUrl: `${process.env.CDN_URL}/${value.Image.SetIconPath}`
}))} }))}
excludeSet={[]} excludeSet={[]}
selectedCustomSet={selectedRelicSet} selectedCustomSet={selectedRelicSet}
@@ -288,15 +289,15 @@ export default function RelicMaker() {
{/* Set Bonus Display */} {/* Set Bonus Display */}
<div className="mb-6 py-4 bg-base-100 rounded-lg"> <div className="mb-6 py-4 bg-base-100 rounded-lg">
{selectedRelicSet !== "" ? Object.entries(mapRelicInfo[selectedRelicSet].RequireNum).map(([key, value]) => ( {selectedRelicSet !== "" ? Object.entries(mapRelicSet[selectedRelicSet].Skills).map(([key, value]) => (
<div key={key} className="text-blue-300 text-sm mb-1"> <div key={key} className="text-blue-300 text-sm mb-1">
<span className="text-info font-bold">{key}-Pc: <span className="text-info font-bold">{key}-Pc:
<div <div
className="text-warning leading-relaxed font-bold" className="text-warning leading-relaxed font-bold"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
value.Desc, getLocaleName(locale, value.Desc),
value.ParamList || [] value.Param || []
) )
}} }}
/> </span> /> </span>
@@ -384,13 +385,13 @@ export default function RelicMaker() {
<SelectCustomImage <SelectCustomImage
customSet={Object.entries(subAffixOptions).map(([key, value]) => ({ customSet={Object.entries(subAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: mappingStats[value.property].icon imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({ excludeSet={Object.entries(exSubAffixOptions).map(([key, value]) => ({
value: key, value: key,
label: mappingStats[value.property].name + " " + mappingStats[value.property].unit, label: mappingStats[value.Property].name + " " + mappingStats[value.Property].unit,
imageUrl: mappingStats[value.property].icon imageUrl: `${process.env.CDN_URL}/${mappingStats[value.Property].icon}`
}))} }))}
selectedCustomSet={v.affixId} selectedCustomSet={v.affixId}
placeholder={transI18n("selectASubStat")} placeholder={transI18n("selectASubStat")}
@@ -401,7 +402,7 @@ export default function RelicMaker() {
{/* Current Value */} {/* Current Value */}
<div className="col-span-4 text-center flex items-center justify-center gap-2"> <div className="col-span-4 text-center flex items-center justify-center gap-2">
<span className="text-2xl font-mono">+{ }</span> <span className="text-2xl font-mono">+{ }</span>
<div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""}</div> <div className="text-xl font-bold text-info">{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount, v.rollCount)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}</div>
</div> </div>
{/* Up Roll Values */} {/* Up Roll Values */}
@@ -415,19 +416,19 @@ export default function RelicMaker() {
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 0)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 1)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 1, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)} onClick={() => handleSubStatChange(v.affixId, index, v.rollCount + 1, v.stepCount + 2)}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], v.stepCount + 2, v.rollCount + 1)}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>
@@ -443,19 +444,19 @@ export default function RelicMaker() {
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], 0, Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 1, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 1, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
<button <button
onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))} onClick={() => handleSubStatChange(v.affixId, index, Math.max(v.rollCount - 1, 0), Math.max(v.stepCount - 2, 0))}
className="btn btn-sm btn-info border-0" className="btn btn-sm btn-info border-0"
> >
{calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.property]?.unit || ""} {calcAffixBonus(subAffixOptions[v.affixId], Math.max(v.stepCount - 2, 0), Math.max(v.rollCount - 1, 0))}{mappingStats?.[subAffixOptions[v.affixId]?.Property]?.unit || ""}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -6,18 +6,18 @@ import { motion } from "framer-motion";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import RelicCard from "../card/relicCard"; import RelicCard from "../card/relicCard";
import useAvatarStore from "@/stores/avatarStore";
import useRelicStore from "@/stores/relicStore";
import useModelStore from '@/stores/modelStore'; import useModelStore from '@/stores/modelStore';
import { replaceByParam } from '@/helper'; import { replaceByParam } from '@/helper';
import useRelicMakerStore from '@/stores/relicMakerStore'; import useRelicMakerStore from '@/stores/relicMakerStore';
import useAffixStore from '@/stores/affixStore';
import QuickView from "../quickView"; import QuickView from "../quickView";
import { ModalConfig } from "@/types"; import { ModalConfig } from "@/types";
import useCurrentDataStore from "@/stores/currentDataStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { getLocaleName } from '@/helper';
import useLocaleStore from "@/stores/localeStore";
export default function RelicsInfo() { export default function RelicsInfo() {
const { avatars, setAvatars } = useUserDataStore() const { avatars, setAvatars } = useUserDataStore()
const { avatarSelected } = useAvatarStore()
const { const {
setSelectedRelicSlot, setSelectedRelicSlot,
selectedRelicSlot, selectedRelicSlot,
@@ -29,7 +29,7 @@ export default function RelicsInfo() {
resetSubStat, resetSubStat,
listSelectedSubStats, listSelectedSubStats,
} = useRelicMakerStore() } = useRelicMakerStore()
const { mapSubAffix } = useAffixStore()
const { const {
isOpenRelic, isOpenRelic,
setIsOpenRelic, setIsOpenRelic,
@@ -37,9 +37,10 @@ export default function RelicsInfo() {
setIsOpenQuickView setIsOpenQuickView
} = useModelStore() } = useModelStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { avatarSelected } = useCurrentDataStore()
const { mapRelicInfo } = useRelicStore() const { mapRelicSet, subAffix } = useDetailDataStore()
const { locale } = useLocaleStore()
const handleShow = (modalId: string) => { const handleShow = (modalId: string) => {
const modal = document.getElementById(modalId) as HTMLDialogElement | null; const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) { if (modal) {
@@ -56,8 +57,8 @@ export default function RelicsInfo() {
}; };
const getRelic = useCallback((slot: string) => { const getRelic = useCallback((slot: string) => {
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
if (avatar) { if (avatar) {
return avatar.profileList[avatar.profileSelect]?.relics[slot] || null; return avatar.profileList[avatar.profileSelect]?.relics[slot] || null;
} }
@@ -65,7 +66,7 @@ export default function RelicsInfo() {
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const handlerDeleteRelic = (slot: string) => { const handlerDeleteRelic = (slot: string) => {
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
if (avatar) { if (avatar) {
delete avatar.profileList[avatar.profileSelect].relics[slot] delete avatar.profileList[avatar.profileSelect].relics[slot]
setAvatars({ ...avatars }); setAvatars({ ...avatars });
@@ -84,7 +85,7 @@ export default function RelicsInfo() {
const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats]; const newSubAffixes: { affixId: string, property: string, rollCount: number, stepCount: number }[] = [...listSelectedSubStats];
relic.sub_affixes.forEach((item, index) => { relic.sub_affixes.forEach((item, index) => {
newSubAffixes[index].affixId = item.sub_affix_id.toString(); newSubAffixes[index].affixId = item.sub_affix_id.toString();
newSubAffixes[index].property = mapSubAffix["5"][item.sub_affix_id.toString()]?.property || ""; newSubAffixes[index].property = subAffix["5"][item.sub_affix_id.toString()]?.Property || "";
newSubAffixes[index].rollCount = item.count || 0; newSubAffixes[index].rollCount = item.count || 0;
newSubAffixes[index].stepCount = item.step || 0; newSubAffixes[index].stepCount = item.step || 0;
}) })
@@ -108,7 +109,7 @@ export default function RelicsInfo() {
} }
const relicEffects = useMemo(() => { const relicEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID.toString() || ""];
const relicCount: { [key: string]: number } = {}; const relicCount: { [key: string]: number } = {};
if (avatar) { if (avatar) {
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) { for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
@@ -199,7 +200,7 @@ export default function RelicsInfo() {
> >
<RelicCard <RelicCard
slot={item} slot={item}
avatarId={avatarSelected?.id || ""} avatarId={avatarSelected?.ID.toString() || ""}
/> />
</div> </div>
@@ -284,7 +285,7 @@ export default function RelicsInfo() {
<div className="space-y-6"> <div className="space-y-6">
{relicEffects.map((setEffect, index) => { {relicEffects.map((setEffect, index) => {
const relicInfo = mapRelicInfo[setEffect.key]; const relicInfo = mapRelicSet[setEffect.key];
if (!relicInfo) return null; if (!relicInfo) return null;
return ( return (
<div key={index} className="space-y-3"> <div key={index} className="space-y-3">
@@ -293,7 +294,7 @@ export default function RelicsInfo() {
className="font-bold text-warning" className="font-bold text-warning"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
relicInfo.Name, getLocaleName(locale, relicInfo.Name),
[] []
) )
}} }}
@@ -306,7 +307,7 @@ export default function RelicsInfo() {
</div> </div>
<div className="space-y-2 pl-4"> <div className="space-y-2 pl-4">
{Object.entries(relicInfo.RequireNum).map(([requireNum, value]) => { {Object.entries(relicInfo.Skills).map(([requireNum, value]) => {
if (Number(requireNum) > Number(setEffect.count)) return null; if (Number(requireNum) > Number(setEffect.count)) return null;
return ( return (
<div key={requireNum} className="space-y-1"> <div key={requireNum} className="space-y-1">
@@ -317,8 +318,8 @@ export default function RelicsInfo() {
className="text-sm text-base-content/80 leading-relaxed pl-4" className="text-sm text-base-content/80 leading-relaxed pl-4"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
value.Desc, getLocaleName(locale, value.Desc),
value.ParamList || [] value.Param || []
) )
}} }}
/> />

View File

@@ -1,6 +1,5 @@
"use client"; "use client";
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'; import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
import useAvatarStore from "@/stores/avatarStore";
import { FastAverageColor } from 'fast-average-color'; import { FastAverageColor } from 'fast-average-color';
import NextImage from 'next/image'; import NextImage from 'next/image';
import ParseText from '../parseText'; import ParseText from '../parseText';
@@ -8,27 +7,24 @@ import useLocaleStore from '@/stores/localeStore';
import { calcAffixBonus, calcBaseStat, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, convertToRoman, getNameChar, replaceByParam } from '@/helper'; import { calcAffixBonus, calcBaseStat, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, convertToRoman, getNameChar, replaceByParam } from '@/helper';
import useUserDataStore from '@/stores/userDataStore'; import useUserDataStore from '@/stores/userDataStore';
import { traceShowCaseMap } from '@/constant/traceConstant'; import { traceShowCaseMap } from '@/constant/traceConstant';
import { StatusAddType } from '@/types';
import { mappingStats } from '@/constant/constant'; import { mappingStats } from '@/constant/constant';
import useLightconeStore from '@/stores/lightconeStore';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import useAffixStore from '@/stores/affixStore';
import useRelicStore from '@/stores/relicStore';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import RelicShowcase from './relicShowcase'; import RelicShowcase from './relicShowcase';
import useDetailDataStore from '@/stores/detailDataStore';
import useCurrentDataStore from '@/stores/currentDataStore';
import { getLocaleName } from '@/helper';
export default function ShowCaseInfo() { export default function ShowCaseInfo() {
const { avatarSelected, mapAvatarInfo } = useAvatarStore() const { avatarSelected } = useCurrentDataStore()
const { mapLightconeInfo } = useLightconeStore() const { mainAffix, subAffix, mapRelicSet, mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore()
const { mapMainAffix, mapSubAffix } = useAffixStore()
const { avatars } = useUserDataStore() const { avatars } = useUserDataStore()
const [avgColor, setAvgColor] = useState('#222'); const [avgColor, setAvgColor] = useState('#222');
const imgRef = useRef(null); const imgRef = useRef(null);
const cardRef = useRef(null) const cardRef = useRef(null)
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { mapRelicInfo } = useRelicStore()
const handleSaveImage = useCallback(() => { const handleSaveImage = useCallback(() => {
if (cardRef.current === null || !avatarSelected) { if (cardRef.current === null || !avatarSelected) {
@@ -52,25 +48,24 @@ export default function ShowCaseInfo() {
link.click(); link.click();
}) })
.catch((e) => { .catch((e) => {
console.log(e)
toast.error("Error generating showcase card!"); toast.error("Error generating showcase card!");
}); });
}, [avatarSelected, locale, transI18n]); }, [avatarSelected, locale, transI18n]);
useEffect(() => { useEffect(() => {
if (!avatarSelected?.id) return; if (!avatarSelected) return;
const fac = new FastAverageColor(); const fac = new FastAverageColor();
const img = new Image(); const img = new Image();
img.crossOrigin = 'anonymous'; img.crossOrigin = 'anonymous';
img.src = `${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`; img.src = `${process.env.CDN_URL}/${avatarSelected?.Image?.AvatarCutinFrontImgPath}`;
img.onload = () => { img.onload = () => {
fac.getColorAsync(img) fac.getColorAsync(img)
.then((color) => { .then((color) => {
setAvgColor(color.hex); setAvgColor(color.hex);
}) })
.catch(e => console.error("Vẫn lỗi CORS:", e)); .catch(e => console.error("Error:", e));
}; };
return () => { return () => {
fac.destroy(); fac.destroy();
@@ -78,22 +73,17 @@ export default function ShowCaseInfo() {
}; };
}, [avatarSelected]); }, [avatarSelected]);
const avatarInfo = useMemo(() => {
if (!avatarSelected) return
return mapAvatarInfo[avatarSelected.id]
}, [avatarSelected, mapAvatarInfo])
const avatarSkillTree = useMemo(() => { const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected.id]) return {} if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected.id].enhanced) { if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {} return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
} }
return avatarInfo?.SkillTrees || {} return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatarInfo, avatars]) }, [avatarSelected, avatars])
const avatarData = useMemo(() => { const avatarData = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return avatars[avatarSelected.id] return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarProfile = useMemo(() => { const avatarProfile = useMemo(() => {
@@ -102,23 +92,23 @@ export default function ShowCaseInfo() {
}, [avatarSelected, avatarData]) }, [avatarSelected, avatarData])
const lightconeStats = useMemo(() => { const lightconeStats = useMemo(() => {
if (!avatarSelected || !avatarProfile?.lightcone || !mapLightconeInfo[avatarProfile?.lightcone?.item_id]) return if (!avatarSelected || !avatarProfile?.lightcone || !mapLightCone[avatarProfile?.lightcone?.item_id]) return
const promotion = calcPromotion(avatarProfile?.lightcone?.level) const promotion = calcPromotion(avatarProfile?.lightcone?.level)
const atkStat = calcBaseStat( const atkStat = calcBaseStat(
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttack, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttack,
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttackAdd, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseAttackAdd,
0, 0,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
const hpStat = calcBaseStat( const hpStat = calcBaseStat(
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHP, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHP,
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHPAdd, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseHPAdd,
0, 0,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
const defStat = calcBaseStat( const defStat = calcBaseStat(
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefence, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefence,
mapLightconeInfo[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefenceAdd, mapLightCone[avatarProfile?.lightcone?.item_id].Stats[promotion].BaseDefenceAdd,
0, 0,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
@@ -127,10 +117,10 @@ export default function ShowCaseInfo() {
hp: hpStat, hp: hpStat,
def: defStat, def: defStat,
} }
}, [avatarSelected, mapLightconeInfo, avatarProfile]) }, [avatarSelected, mapLightCone, avatarProfile])
const relicEffects = useMemo(() => { const relicEffects = useMemo(() => {
const avatar = avatars[avatarSelected?.id || ""]; const avatar = avatars[avatarSelected?.ID?.toString() || ""];
const relicCount: { [key: string]: number } = {}; const relicCount: { [key: string]: number } = {};
if (avatar) { if (avatar) {
for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) { for (const relic of Object.values(avatar.profileList[avatar.profileSelect].relics)) {
@@ -151,45 +141,45 @@ export default function ShowCaseInfo() {
}, [avatars, avatarSelected]); }, [avatars, avatarSelected]);
const relicStats = useMemo(() => { const relicStats = useMemo(() => {
if (!avatarSelected || !avatarProfile?.relics || !mapMainAffix || !mapSubAffix) return if (!avatarSelected || !avatarProfile?.relics || !mainAffix || !subAffix) return
return Object.entries(avatarProfile?.relics).map(([key, value]) => { return Object.entries(avatarProfile?.relics).map(([key, value]) => {
const mainAffixMap = mapMainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = mapSubAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
return { return {
img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`, img: `${process.env.CDN_URL}/spriteoutput/relicfigures/IconRelic_${value.relic_set_id}_${key}.png`,
mainAffix: { mainAffix: {
property: mainAffixMap?.[value?.main_affix_id]?.property, property: mainAffixMap?.[value?.main_affix_id]?.Property,
level: value?.level, level: value?.level,
valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level), valueAffix: calcMainAffixBonus(mainAffixMap?.[value?.main_affix_id], value?.level),
detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.property] detail: mappingStats?.[mainAffixMap?.[value?.main_affix_id]?.Property]
}, },
subAffix: value?.sub_affixes?.map((subValue) => { subAffix: value?.sub_affixes?.map((subValue) => {
return { return {
property: subAffixMap?.[subValue?.sub_affix_id]?.property, property: subAffixMap?.[subValue?.sub_affix_id]?.Property,
valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count), valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count),
detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property], detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.Property],
step: subValue?.step, step: subValue?.step,
count: subValue?.count count: subValue?.count
} }
}) })
} }
}) })
}, [avatarSelected, avatarProfile, mapMainAffix, mapSubAffix]) }, [avatarSelected, avatarProfile, mainAffix, subAffix])
const totalSubStats = useMemo(() => { const totalSubStats = useMemo(() => {
if (!relicStats?.length) return 0 if (!relicStats?.length) return 0
return (relicStats ?? []).reduce((acc, relic) => { return (relicStats ?? []).reduce((acc, relic) => {
const subAffixList = relic?.subAffix ?? [] const subAffixList = relic?.subAffix ?? []
return acc + subAffixList.reduce((subAcc, subAffix) => { return acc + subAffixList.reduce((subAcc, subAffix) => {
if (avatarInfo?.Relics?.SubAffixPropertyList.findIndex(it => it === subAffix.property) !== -1) { if (avatarSelected?.Relics?.SubAffixPropertyList.findIndex(it => it === subAffix.property) !== -1) {
return subAcc + (subAffix?.count ?? 0) return subAcc + (subAffix?.count ?? 0)
} }
return subAcc return subAcc
}, 0) }, 0)
}, 0) }, 0)
}, [relicStats, avatarInfo]) }, [relicStats, avatarSelected])
const characterStats = useMemo(() => { const characterStats = useMemo(() => {
if (!avatarSelected || !avatarData) return if (!avatarSelected || !avatarData) return
@@ -205,73 +195,73 @@ export default function ShowCaseInfo() {
}> = { }> = {
HP: { HP: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.HPAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.HPAdd,
avatarData.level avatarData.level
), ),
name: "HP", name: "HP",
icon: "/icon/hp.webp", icon: "spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "", unit: "",
round: 0 round: 0
}, },
ATK: { ATK: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.AttackAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.AttackAdd,
avatarData.level avatarData.level
), ),
name: "ATK", name: "ATK",
icon: "/icon/attack.webp", icon: "spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "", unit: "",
round: 0 round: 0
}, },
DEF: { DEF: {
value: calcBaseStatRaw( value: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
base: calcBaseStatRaw( base: calcBaseStatRaw(
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceBase, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceBase,
mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.DefenceAdd, mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.DefenceAdd,
avatarData.level avatarData.level
), ),
name: "DEF", name: "DEF",
icon: "/icon/defence.webp", icon: "spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "", unit: "",
round: 0 round: 0
}, },
SPD: { SPD: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.SpeedBase || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.SpeedBase || 0,
name: "SPD", name: "SPD",
icon: "/icon/speed.webp", icon: "spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
round: 1 round: 1
}, },
CRITRate: { CRITRate: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalChance || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalChance || 0,
name: "CRIT Rate", name: "CRIT Rate",
icon: "/icon/crit-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
CRITDmg: { CRITDmg: {
value: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0, value: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
base: mapAvatarInfo?.[avatarSelected.id]?.Stats[charPromotion]?.CriticalDamage || 0, base: mapAvatar?.[avatarSelected?.ID?.toString()]?.Stats[charPromotion]?.CriticalDamage || 0,
name: "CRIT DMG", name: "CRIT DMG",
icon: "/icon/crit-damage.webp", icon: "spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -279,7 +269,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Break Effect", name: "Break Effect",
icon: "/icon/break-effect.webp", icon: "spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -287,7 +277,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect RES", name: "Effect RES",
icon: "/icon/effect-res.webp", icon: "spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -295,7 +285,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Energy Rate", name: "Energy Rate",
icon: "/icon/energy-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -303,7 +293,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Effect Hit Rate", name: "Effect Hit Rate",
icon: "/icon/effect-hit-rate.webp", icon: "spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -311,7 +301,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Healing Boost", name: "Healing Boost",
icon: "/icon/healing-boost.webp", icon: "spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -319,7 +309,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Physical Boost", name: "Physical Boost",
icon: "/icon/physical-add.webp", icon: "spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -327,7 +317,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Fire Boost", name: "Fire Boost",
icon: "/icon/fire-add.webp", icon: "spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -335,7 +325,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Ice Boost", name: "Ice Boost",
icon: "/icon/ice-add.webp", icon: "spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -343,7 +333,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Thunder Boost", name: "Thunder Boost",
icon: "/icon/thunder-add.webp", icon: "spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -351,7 +341,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Wind Boost", name: "Wind Boost",
icon: "/icon/wind-add.webp", icon: "spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -359,7 +349,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Quantum Boost", name: "Quantum Boost",
icon: "/icon/quantum-add.webp", icon: "spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -367,7 +357,7 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Imaginary Boost", name: "Imaginary Boost",
icon: "/icon/imaginary-add.webp", icon: "spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%", unit: "%",
round: 1 round: 1
}, },
@@ -375,57 +365,57 @@ export default function ShowCaseInfo() {
value: 0, value: 0,
base: 0, base: 0,
name: "Elation Boost", name: "Elation Boost",
icon: "/icon/IconJoy.webp", icon: "spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%", unit: "%",
round: 1 round: 1
} }
} }
if (avatarProfile?.lightcone && mapLightconeInfo[avatarProfile?.lightcone?.item_id]) { if (avatarProfile?.lightcone && mapLightCone[avatarProfile?.lightcone?.item_id]) {
const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level) const lightconePromotion = calcPromotion(avatarProfile?.lightcone?.level)
statsData.HP.value += calcBaseStatRaw( statsData.HP.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.HP.base += calcBaseStatRaw( statsData.HP.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHP,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseHPAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.value += calcBaseStatRaw( statsData.ATK.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.ATK.base += calcBaseStatRaw( statsData.ATK.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttack,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseAttackAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.value += calcBaseStatRaw( statsData.DEF.value += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
statsData.DEF.base += calcBaseStatRaw( statsData.DEF.base += calcBaseStatRaw(
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefence,
mapLightconeInfo?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd, mapLightCone?.[avatarProfile?.lightcone?.item_id]?.Stats[lightconePromotion]?.BaseDefenceAdd,
avatarProfile?.lightcone?.level avatarProfile?.lightcone?.level
) )
const bonusData = mapLightconeInfo[avatarProfile?.lightcone?.item_id].Bonus?.[avatarProfile?.lightcone.rank - 1] const bonusData = mapLightCone[avatarProfile?.lightcone?.item_id]?.Skills?.Level?.[avatarProfile?.lightcone.rank]?.Bonus
if (bonusData && bonusData.length > 0) { if (bonusData && bonusData.length > 0) {
const bonusSpd = bonusData.filter((bonus) => bonus.type === "BaseSpeed") const bonusSpd = bonusData.filter((bonus) => bonus.PropertyType === "BaseSpeed")
const bonusOther = bonusData.filter((bonus) => bonus.type !== "BaseSpeed") const bonusOther = bonusData.filter((bonus) => bonus.PropertyType !== "BaseSpeed")
bonusSpd.forEach((bonus) => { bonusSpd.forEach((bonus) => {
statsData.SPD.value += bonus.value statsData.SPD.value += bonus.Value
statsData.SPD.base += bonus.value statsData.SPD.base += bonus.Value
}) })
bonusOther.forEach((bonus) => { bonusOther.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.type]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
} }
@@ -448,19 +438,17 @@ export default function ShowCaseInfo() {
}) })
} }
if (avatarProfile?.relics && mainAffix && subAffix) {
if (avatarProfile?.relics && mapMainAffix && mapSubAffix) {
Object.entries(avatarProfile?.relics).forEach(([key, value]) => { Object.entries(avatarProfile?.relics).forEach(([key, value]) => {
const mainAffixMap = mapMainAffix["5" + key] const mainAffixMap = mainAffix["5" + key]
const subAffixMap = mapSubAffix["5"] const subAffixMap = subAffix["5"]
if (!mainAffixMap || !subAffixMap) return if (!mainAffixMap || !subAffixMap) return
const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.property]?.baseStat const mainStats = mappingStats?.[mainAffixMap?.[value.main_affix_id]?.Property]?.baseStat
if (mainStats && statsData[mainStats]) { if (mainStats && statsData[mainStats]) {
statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base) statsData[mainStats].value += calcMainAffixBonusRaw(mainAffixMap?.[value.main_affix_id], value.level, statsData[mainStats].base)
} }
value?.sub_affixes.forEach((subValue) => { value?.sub_affixes.forEach((subValue) => {
const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.property]?.baseStat const subStats = mappingStats?.[subAffixMap?.[subValue.sub_affix_id]?.Property]?.baseStat
if (subStats && statsData[subStats]) { if (subStats && statsData[subStats]) {
statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base) statsData[subStats].value += calcSubAffixBonusRaw(subAffixMap?.[subValue.sub_affix_id], subValue.step, subValue.count, statsData[subStats].base)
} }
@@ -470,14 +458,14 @@ export default function ShowCaseInfo() {
if (relicEffects && relicEffects.length > 0) { if (relicEffects && relicEffects.length > 0) {
relicEffects.forEach((relic) => { relicEffects.forEach((relic) => {
const dataBonus = mapRelicInfo?.[relic.key]?.Bonus const dataBonus = mapRelicSet?.[relic.key]?.Skills
if (!dataBonus || Object.keys(dataBonus).length === 0) return if (!dataBonus || Object.keys(dataBonus).length === 0) return
Object.entries(dataBonus || {}).forEach(([key, value]) => { Object.entries(dataBonus || {}).forEach(([key, value]) => {
if (relic.count < Number(key)) return if (relic.count < Number(key)) return
value.forEach((bonus) => { value.Bonus.forEach((bonus) => {
const statsBase = mappingStats?.[bonus.type]?.baseStat const statsBase = mappingStats?.[bonus.PropertyType]?.baseStat
if (statsBase && statsData[statsBase]) { if (statsBase && statsData[statsBase]) {
statsData[statsBase].value += calcBonusStatRaw(bonus.type, statsData[statsBase].base, bonus.value) statsData[statsBase].value += calcBonusStatRaw(bonus.PropertyType, statsData[statsBase].base, bonus.Value)
} }
}) })
}) })
@@ -489,14 +477,14 @@ export default function ShowCaseInfo() {
}, [ }, [
avatarSelected, avatarSelected,
avatarData, avatarData,
mapAvatarInfo, mapAvatar,
avatarProfile?.lightcone, avatarProfile?.lightcone,
avatarProfile?.relics, avatarProfile?.relics,
mapLightconeInfo, mapLightCone,
mapMainAffix, mainAffix,
mapSubAffix, subAffix,
relicEffects, relicEffects,
mapRelicInfo, mapRelicSet,
avatarSkillTree avatarSkillTree
]) ])
@@ -508,19 +496,6 @@ export default function ShowCaseInfo() {
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
}, []) }, [])
const getImageSkill = useCallback((icon: string | undefined, status: StatusAddType | undefined) => {
if (!icon) return
if (icon.startsWith("SkillIcon")) {
return `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/${icon}`
} else if (status && mappingStats[status.PropertyType]) {
return mappingStats[status.PropertyType].icon
}
else if (icon.startsWith("Icon")) {
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
}
return ""
}, [avatarSelected?.id])
return ( return (
<div className="flex flex-col justify-start m-1 text-white"> <div className="flex flex-col justify-start m-1 text-white">
<div className="flex items-center justify-start mt-4 mb-4"> <div className="flex items-center justify-start mt-4 mb-4">
@@ -550,7 +525,7 @@ export default function ShowCaseInfo() {
ref={imgRef} ref={imgRef}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/avatardrawcard/${avatarSelected?.id}.png`} src={`${process.env.CDN_URL}/${avatarSelected?.Image?.AvatarCutinFrontImgPath}`}
className="object-contain scale-[2] overflow-hidden" className="object-contain scale-[2] overflow-hidden"
alt="Character Preview" alt="Character Preview"
width={1024} width={1024}
@@ -576,9 +551,9 @@ export default function ShowCaseInfo() {
> >
<div className="absolute top-4 left-3"> <div className="absolute top-4 left-3">
{avatarSelected && avatarInfo && avatarData?.data && typeof avatarData?.data?.rank === "number" && ( {avatarSelected && avatarSelected && avatarData?.data && typeof avatarData?.data?.rank === "number" && (
<div className="flex flex-col items-center gap-2 py-2"> <div className="flex flex-col items-center gap-2 py-2">
{avatarInfo?.RankIcon?.map((src, index) => { {Object.values(avatarSelected?.Ranks || {})?.map((rank, index) => {
const isActive = avatarData?.data?.rank > index; const isActive = avatarData?.data?.rank > index;
return ( return (
<div <div
@@ -621,7 +596,7 @@ export default function ShowCaseInfo() {
}} }}
> >
<NextImage <NextImage
src={src ?? null} src={`${process.env.CDN_URL}/${rank.Icon}`}
alt="Rank Icon" alt="Rank Icon"
width={125} width={125}
height={125} height={125}
@@ -664,20 +639,20 @@ export default function ShowCaseInfo() {
<NextImage <NextImage
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${baseType[avatarSelected?.BaseType]?.Icon}`}
alt="Path Icon" alt="Path Icon"
width={32} width={32}
height={32} height={32}
className="h-auto w-8" className="h-8 w-8"
/> />
<NextImage <NextImage
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${avatarSelected?.damageType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${damageType[avatarSelected?.DamageType]?.Icon}`}
alt="Element Icon" alt="Element Icon"
width={32} width={32}
height={32} height={32}
className="h-auto w-8" /> className="h-8 w-8" />
</div> </div>
)} )}
@@ -691,7 +666,7 @@ export default function ShowCaseInfo() {
<NextImage <NextImage
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/icon/${avatarSelected?.baseType.toLowerCase()}.webp`} src={`${process.env.CDN_URL}/${baseType[avatarSelected?.BaseType]?.Icon}`}
alt="Path Icon" alt="Path Icon"
width={160} width={160}
height={160} height={160}
@@ -701,8 +676,8 @@ export default function ShowCaseInfo() {
)} )}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{avatarData && avatarInfo && avatarSkillTree && traceShowCaseMap[avatarSelected?.baseType || ""] {avatarData && avatarSelected && avatarSkillTree && traceShowCaseMap[avatarSelected?.BaseType || ""]
&& Object.values(traceShowCaseMap[avatarSelected?.baseType || ""] || []).map((item, index) => { && Object.values(traceShowCaseMap[avatarSelected?.BaseType || ""] || []).map((item, index) => {
return ( return (
<div key={`row-${index}`} className="flex flex-row items-center"> <div key={`row-${index}`} className="flex flex-row items-center">
@@ -742,26 +717,15 @@ export default function ShowCaseInfo() {
${avatarData.data.skills[avatarSkillTree?.[btn.id]?.["1"]?.PointID] ? "" : "opacity-50"} ${avatarData.data.skills[avatarSkillTree?.[btn.id]?.["1"]?.PointID] ? "" : "opacity-50"}
`} `}
> >
{ <NextImage
(() => { src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`}
const skillImg = getImageSkill( unoptimized
avatarInfo.SkillTrees?.[btn.id]?.["1"]?.Icon, crossOrigin="anonymous"
avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0] alt={btn.id}
); width={125}
height={125}
return skillImg ? ( className={`h-full ${imageSize} ${filterClass}`}
<NextImage />
src={skillImg}
unoptimized
crossOrigin="anonymous"
alt={btn.id}
width={125}
height={125}
className={`h-full ${imageSize} ${filterClass}`}
/>
) : null;
})()
}
{(isBig || isBigMemory) && ( {(isBig || isBigMemory) && (
<span className="absolute bottom-0 left-0 text-[12px] text-white bg-black/70 px-1 rounded-sm"> <span className="absolute bottom-0 left-0 text-[12px] text-white bg-black/70 px-1 rounded-sm">
@@ -872,8 +836,8 @@ export default function ShowCaseInfo() {
> >
{[...Array( {[...Array(
Number( Number(
mapLightconeInfo[avatarProfile?.lightcone?.item_id]?.Rarity?.[ mapLightCone[avatarProfile?.lightcone?.item_id]?.Rarity?.[
mapLightconeInfo[avatarProfile?.lightcone?.item_id]?.Rarity.length - 1 mapLightCone[avatarProfile?.lightcone?.item_id]?.Rarity.length - 1
] || 0 ] || 0
) )
)].map((_, i) => ( )].map((_, i) => (
@@ -890,7 +854,7 @@ export default function ShowCaseInfo() {
<ParseText <ParseText
className="text-lg font-semibold" className="text-lg font-semibold"
locale={locale} locale={locale}
text={mapLightconeInfo[avatarProfile?.lightcone?.item_id].Name} text={getLocaleName(locale, mapLightCone[avatarProfile?.lightcone?.item_id].Name)}
/> />
</div> </div>
@@ -910,7 +874,7 @@ export default function ShowCaseInfo() {
<NextImage <NextImage
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/icon/hp.webp" src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconMaxHP.png`}
alt="HP" alt="HP"
width={16} width={16}
height={16} height={16}
@@ -922,7 +886,7 @@ export default function ShowCaseInfo() {
</div> </div>
<div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1"> <div className="flex items-center gap-1 rounded bg-black/30 px-1 w-fit py-1">
<NextImage <NextImage
src="/icon/attack.webp" src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconAttack.png`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt="ATK" alt="ATK"
@@ -938,7 +902,7 @@ export default function ShowCaseInfo() {
<NextImage <NextImage
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src="/icon/defence.webp" src={`${process.env.CDN_URL}/spriteoutput/ui/avatar/icon/IconDefence.png`}
alt="DEF" alt="DEF"
width={16} width={16}
height={16} height={16}
@@ -964,13 +928,13 @@ export default function ShowCaseInfo() {
return ( return (
<div key={index} className="flex flex-row items-center justify-between"> <div key={index} className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<NextImage src={stat?.icon || ""} <NextImage src={`${process.env.CDN_URL}/${stat?.icon}`}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
alt="Stat Icon" alt="Stat Icon"
width={40} width={40}
height={40} height={40}
className="h-auto w-10 p-2" className="h-10 w-10 p-2"
/> />
<span className="font-bold">{stat.name}</span> <span className="font-bold">{stat.name}</span>
</div> </div>
@@ -986,7 +950,7 @@ export default function ShowCaseInfo() {
<div className="flex flex-col items-center gap-1 w-full my-2"> <div className="flex flex-col items-center gap-1 w-full my-2">
{relicEffects.map((setEffect, index) => { {relicEffects.map((setEffect, index) => {
const relicInfo = mapRelicInfo[setEffect.key]; const relicInfo = mapRelicSet[setEffect.key];
if (!relicInfo) return null; if (!relicInfo) return null;
return ( return (
<div key={index} className="flex w-full flex-row justify-between text-left"> <div key={index} className="flex w-full flex-row justify-between text-left">
@@ -997,7 +961,7 @@ export default function ShowCaseInfo() {
}} }}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
relicInfo.Name, getLocaleName(locale, relicInfo.Name),
[] []
) )
}} }}
@@ -1015,9 +979,9 @@ export default function ShowCaseInfo() {
<div className="flex h-162.5 flex-col justify-between py-3 mr-1 text-lg w-full" > <div className="flex h-162.5 flex-col justify-between py-3 mr-1 text-lg w-full" >
{relicStats?.map((relic, index) => { {relicStats?.map((relic, index) => {
if (!relic || !avatarInfo) return null if (!relic || !avatarSelected) return null
return ( return (
<RelicShowcase key={index} relic={relic} avatarInfo={avatarInfo} /> <RelicShowcase key={index} relic={relic} avatarInfo={avatarSelected} />
) )
})} })}

View File

@@ -2,14 +2,14 @@
"use client" "use client"
import NextImage from "next/image" import NextImage from "next/image"
import { CharacterDetail, RelicShowcaseType } from "@/types"; import { AvatarDetail, RelicShowcaseType } from "@/types";
export default function RelicShowcase({ export default function RelicShowcase({
relic, relic,
avatarInfo, avatarInfo,
}: { }: {
relic: RelicShowcaseType; relic: RelicShowcaseType;
avatarInfo: CharacterDetail; avatarInfo: AvatarDetail;
}) { }) {
return ( return (
<> <>
@@ -28,7 +28,7 @@ export default function RelicShowcase({
width={78} width={78}
height={78} height={78}
alt="Relic Icon" alt="Relic Icon"
className="h-auto w-19.5 rounded-lg" className="h-19.5 w-19.5 rounded-lg"
/> />
<div <div
@@ -48,13 +48,13 @@ export default function RelicShowcase({
<div className="relative"> <div className="relative">
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div> <div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
<NextImage <NextImage
src={relic?.mainAffix?.detail?.icon || ""} src={`${process.env.CDN_URL}/${relic?.mainAffix?.detail?.icon}` || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={35} width={35}
height={35} height={35}
alt="Main Affix Icon" alt="Main Affix Icon"
className="h-auto w-8.75" className="h-8.75 w-8.75"
/> />
</div> </div>
<span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]"> <span className="text-base text-yellow-400 font-semibold drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]">
@@ -75,13 +75,13 @@ export default function RelicShowcase({
<div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0"> <div className="relative flex flex-row items-center bg-black/20 backdrop-blur-sm rounded-md p-1 border border-white/5 min-w-0">
{subAffix?.detail?.icon ? ( {subAffix?.detail?.icon ? (
<NextImage <NextImage
src={subAffix?.detail?.icon || ""} src={`${process.env.CDN_URL}/${subAffix?.detail?.icon}` || ""}
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
width={32} width={32}
height={32} height={32}
alt="Sub Affix Icon" alt="Sub Affix Icon"
className="h-auto w-6 shrink-0" className="h-6 w-6 shrink-0"
/> />
) : ( ) : (
<div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0"> <div className="h-6 w-6 bg-black/60 rounded flex items-center justify-center border border-white/10 shrink-0">

View File

@@ -1,64 +1,47 @@
"use client" "use client"
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useAvatarStore from "@/stores/avatarStore";
import { useMemo } from "react"; import { useMemo } from "react";
import { traceButtonsInfo, traceLink } from "@/constant/traceConstant"; import { traceButtonsInfo, traceLink } from "@/constant/traceConstant";
import useUserDataStore from "@/stores/userDataStore"; import useUserDataStore from "@/stores/userDataStore";
import useLocaleStore from "@/stores/localeStore"; import useLocaleStore from "@/stores/localeStore";
import Image from "next/image"; import Image from "next/image";
import { replaceByParam } from "@/helper"; import { replaceByParam, getLocaleName } from '@/helper';
import { mappingStats } from "@/constant/constant"; import { mappingStats } from "@/constant/constant";
import { StatusAddType } from "@/types";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import useCurrentDataStore from "@/stores/currentDataStore";
import { StatusAdd } from '@/types/avatarDetail';
export default function SkillsInfo() { export default function SkillsInfo() {
const transI18n = useTranslations("DataPage") const transI18n = useTranslations("DataPage")
const { theme } = useLocaleStore() const { theme } = useLocaleStore()
const { avatarSelected, mapAvatarInfo, skillSelected, setSkillSelected } = useAvatarStore() const { avatarSelected, skillIDSelected, setSkillIDSelected } = useCurrentDataStore()
const { avatars, setAvatar } = useUserDataStore() const { avatars, setAvatar } = useUserDataStore()
const { locale } = useLocaleStore()
const traceButtons = useMemo(() => { const traceButtons = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return traceButtonsInfo[avatarSelected.baseType] return traceButtonsInfo[avatarSelected.BaseType]
}, [avatarSelected]) }, [avatarSelected])
const avatarInfo = useMemo(() => {
if (!avatarSelected) return
return mapAvatarInfo[avatarSelected.id]
}, [avatarSelected, mapAvatarInfo])
const avatarData = useMemo(() => { const avatarData = useMemo(() => {
if (!avatarSelected) return if (!avatarSelected) return
return avatars[avatarSelected.id] return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars]) }, [avatarSelected, avatars])
const avatarSkillTree = useMemo(() => { const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected.id]) return {} if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected.id].enhanced) { if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {} return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
} }
return avatarInfo?.SkillTrees || {} return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatarInfo, avatars]) }, [avatarSelected, avatars])
const skillInfo = useMemo(() => { const skillInfo = useMemo(() => {
if (!avatarSelected || !skillSelected) return if (!avatarSelected || !skillIDSelected) return
return avatarSkillTree?.[skillSelected || ""]?.["1"] return avatarSkillTree?.[skillIDSelected || ""]?.["1"]
}, [avatarSelected, avatarSkillTree, skillSelected]) }, [avatarSelected, avatarSkillTree, skillIDSelected])
const getImageSkill = (icon: string | undefined, status: StatusAddType | undefined) => { const getTraceBuffDisplay = (status: StatusAdd) => {
if (!icon) return
const urlPrefix = `${process.env.CDN_URL}/spriteoutput/skillicons/avatar/${avatarSelected?.id}/`;
if (icon.startsWith("SkillIcon")) {
return `${urlPrefix}${icon}`
} else if (status && mappingStats[status.PropertyType]) {
return mappingStats[status.PropertyType].icon
} else if (icon.startsWith("Icon")) {
return `${process.env.CDN_URL}/spriteoutput/trace/${icon}`
}
}
const getTraceBuffDisplay = (status: StatusAddType) => {
const dataDisplay = mappingStats[status.PropertyType] const dataDisplay = mappingStats[status.PropertyType]
if (!dataDisplay) return "" if (!dataDisplay) return ""
if (dataDisplay.unit === "%") { if (dataDisplay.unit === "%") {
@@ -72,11 +55,12 @@ export default function SkillsInfo() {
const dataLevelUpSkill = useMemo(() => { const dataLevelUpSkill = useMemo(() => {
const skillIds: number[] = skillInfo?.LevelUpSkillID || [] const skillIds: number[] = skillInfo?.LevelUpSkillID || []
if (!avatarSelected || !avatarInfo || !avatarData) return if (!avatarSelected || !avatarData) return undefined
let result = Object.values(avatarInfo.Skills || {})?.filter((skill) => skillIds.includes(skill.Id)) let result = Object.values(avatarSelected.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
if (avatarData.enhanced) { if (avatarData.enhanced) {
result = Object.values(avatarInfo.Enhanced[avatarData.enhanced.toString()].Skills || {})?.filter((skill) => skillIds.includes(skill.Id)) result = Object.values(avatarSelected?.Enhanced?.[avatarData.enhanced.toString()]?.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
} }
if (result && result.length > 0) { if (result && result.length > 0) {
return { return {
isServant: false, isServant: false,
@@ -84,25 +68,22 @@ export default function SkillsInfo() {
servantData: null, servantData: null,
} }
} }
const resultServant = Object.entries(avatarInfo.Memosprite?.Skills || {}) const resultServant = Object.values(avatarSelected?.Memosprite?.Skills || {})
?.filter(([skillId]) => skillIds.includes(Number(skillId))) ?.filter((skill) => skillIds.includes(skill.ID))
?.map(([skillId, skillData]) => ({
Id: Number(skillId),
...skillData,
}))
if (resultServant && resultServant.length > 0) { if (resultServant && resultServant.length > 0) {
return { return {
isServant: true, isServant: true,
data: resultServant, data: resultServant,
servantData: avatarInfo.Memosprite, servantData: avatarSelected.Memosprite,
} }
} }
return undefined return undefined
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarInfo, avatarData]) }, [skillInfo?.LevelUpSkillID, avatarSelected, avatarData])
const handlerMaxAll = () => { const handlerMaxAll = () => {
if (!avatarInfo || !avatarData || !avatarSkillTree) { if (!avatarData || !avatarSkillTree) {
toast.error(transI18n("maxAllFailed")) toast.error(transI18n("maxAllFailed"))
return return
} }
@@ -123,8 +104,8 @@ export default function SkillsInfo() {
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0 newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
if (!status && traceLink?.[avatarSelected?.baseType || ""]?.[skillSelected || ""]) { if (!status && traceLink?.[avatarSelected?.BaseType || ""]?.[skillIDSelected || ""]) {
traceLink[avatarSelected?.baseType || ""][skillSelected || ""].forEach((pointId) => { traceLink[avatarSelected?.BaseType || ""][skillIDSelected || ""].forEach((pointId) => {
if (avatarSkillTree?.[pointId]?.["1"]) { if (avatarSkillTree?.[pointId]?.["1"]) {
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0 newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
} }
@@ -143,12 +124,12 @@ export default function SkillsInfo() {
</h2> </h2>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button> <button className="btn btn-success" onClick={handlerMaxAll}>{transI18n("maxAll")}</button>
{traceButtons && avatarInfo && ( {traceButtons && avatarSelected && (
<div className="grid col-span-4 relative w-full aspect-square"> <div className="grid col-span-4 relative w-full aspect-square">
<Image <Image
unoptimized unoptimized
crossOrigin="anonymous" crossOrigin="anonymous"
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`} src={`/skilltree/${avatarSelected?.BaseType?.toUpperCase()}.webp`}
alt="" alt=""
width={312} width={312}
priority={true} priority={true}
@@ -159,7 +140,7 @@ export default function SkillsInfo() {
className={`w-full h-full object-cover rounded-xl`} className={`w-full h-full object-cover rounded-xl`}
/> />
{traceButtons.map((btn, index) => { {traceButtons.map((btn, index) => {
if (!avatarInfo?.SkillTrees?.[btn.id]) { if (!avatarSelected?.SkillTrees?.[btn.id]) {
return null return null
} }
return ( return (
@@ -178,13 +159,13 @@ export default function SkillsInfo() {
${btn.size === "special" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""} ${btn.size === "special" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-white" : ""}
${btn.size === "memory" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""} ${btn.size === "memory" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""} ${btn.size === "elation" ? "w-[9vw] h-[9vw] md:w-[3.5vw] md:h-[3.5vw] bg-black" : ""}
${skillSelected === btn.id ? "border-4 border-primary" : ""} ${skillIDSelected === btn.id ? "border-4 border-primary" : ""}
${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID] ${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]
? "opacity-50 cursor-not-allowed" ? "opacity-50 cursor-not-allowed"
: ""} : ""}
`} `}
onClick={() => { onClick={() => {
setSkillSelected(btn.id === skillSelected ? null : btn.id) setSkillIDSelected(btn.id === skillIDSelected ? null : btn.id)
}} }}
style={{ style={{
left: btn.left, left: btn.left,
@@ -193,7 +174,7 @@ export default function SkillsInfo() {
}} }}
> >
<Image <Image
src={getImageSkill(avatarInfo?.SkillTrees?.[btn.id]?.["1"]?.Icon, avatarSkillTree?.[btn.id]?.["1"]?.StatusAddList[0]) || ""} src={`${process.env.CDN_URL}/${avatarSelected?.SkillTrees?.[btn.id]?.["1"]?.Icon}`}
alt={btn.id.replaceAll("Point", "")} alt={btn.id.replaceAll("Point", "")}
priority={true} priority={true}
unoptimized unoptimized
@@ -201,6 +182,8 @@ export default function SkillsInfo() {
width={124} width={124}
height={124} height={124}
style={{ style={{
objectFit: "contain",
padding: "2px",
filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : "" filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : ""
}} }}
/> />
@@ -272,7 +255,7 @@ export default function SkillsInfo() {
</div> </div>
)} )}
{!traceButtons && avatarInfo && ( {!traceButtons && avatarSelected && (
<div className="flex flex-col relative w-full aspect-square"> <div className="flex flex-col relative w-full aspect-square">
</div> </div>
@@ -285,7 +268,7 @@ export default function SkillsInfo() {
<div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div> <div className="w-2 h-6 bg-linear-to-b from-primary to-primary/50 rounded-full"></div>
{transI18n("details")} {transI18n("details")}
</h2> </h2>
{skillSelected && avatarInfo?.SkillTrees && avatarData && ( {skillIDSelected && avatarSelected?.SkillTrees && avatarData && (
<div> <div>
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? ( {skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
<div> <div>
@@ -309,14 +292,14 @@ export default function SkillsInfo() {
</div> </div>
</div> </div>
</div> </div>
) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillSelected)?.size !== "big" ? ( ) : skillInfo?.MaxLevel && skillInfo?.MaxLevel === 1 && traceButtons?.find((btn) => btn.id === skillIDSelected)?.size !== "big" ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1} checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1}
className="toggle toggle-success" className="toggle toggle-success"
onChange={(e) => { onChange={(e) => {
if (traceButtons?.find((btn) => btn.id === skillSelected)?.size === "special") { if (traceButtons?.find((btn) => btn.id === skillIDSelected)?.size === "special") {
if (e.target.checked) { if (e.target.checked) {
const newData = structuredClone(avatarData) const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = 1 newData.data.skills[skillInfo?.PointID] = 1
@@ -343,7 +326,7 @@ export default function SkillsInfo() {
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0)) (skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
&& ( && (
<div className="text-xl font-bold flex items-center gap-2 mt-2"> <div className="text-xl font-bold flex items-center gap-2 mt-2">
{skillInfo.PointName} {getLocaleName(locale, skillInfo.PointName)}
{skillInfo.StatusAddList.length > 0 && ( {skillInfo.StatusAddList.length > 0 && (
<div> <div>
{skillInfo.StatusAddList.map((status, index) => ( {skillInfo.StatusAddList.map((status, index) => (
@@ -360,8 +343,8 @@ export default function SkillsInfo() {
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
skillInfo?.PointDesc || "", getLocaleName(locale, skillInfo?.PointDesc) || "",
skillInfo?.ParamList || [] skillInfo?.Param || []
) )
}} }}
/> />
@@ -377,19 +360,19 @@ export default function SkillsInfo() {
<div key={index}> <div key={index}>
<div className="text-xl font-bold text-primary"> <div className="text-xl font-bold text-primary">
{transI18n(dataLevelUpSkill.isServant ? `${skill?.Type ? "severaltalent" : "servantskill"}` : `${skill?.Type ? skill?.Type.toLowerCase() : "talent"}`)} {transI18n(dataLevelUpSkill.isServant ? `${skill?.AttackType ? "severaltalent" : "servantskill"}` : `${skill?.AttackType ? skill?.AttackType.toLowerCase() : "talent"}`)}
{` (${transI18n(skill.Tag.toLowerCase())})`} {` (${transI18n(skill?.SkillEffect?.toLowerCase())})`}
</div> </div>
<div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(skill.Name, []) }}> <div className="text-lg font-bold" dangerouslySetInnerHTML={{ __html: replaceByParam(getLocaleName(locale, skill.Name), []) }}>
</div> </div>
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: replaceByParam( __html: replaceByParam(
skill.Desc || skill.SimpleDesc, getLocaleName(locale, skill.Desc),
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.ParamList || [] skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.Param || []
) )
}} }}
/> />

View File

@@ -17,145 +17,145 @@ export const listCurrentLanguageApi : Record<string, string> = {
export const mappingStats = <Record<string, {name: string, icon: string, unit: string, baseStat: string}> > { export const mappingStats = <Record<string, {name: string, icon: string, unit: string, baseStat: string}> > {
"HPDelta": { "HPDelta": {
name:"HP", name:"HP",
icon:"/icon/hp.webp", icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "", unit: "",
baseStat: "HP" baseStat: "HP"
}, },
"AttackDelta": { "AttackDelta": {
name:"ATK", name:"ATK",
icon:"/icon/attack.webp", icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "", unit: "",
baseStat: "ATK" baseStat: "ATK"
}, },
"HPAddedRatio": { "HPAddedRatio": {
name:"HP", name:"HP",
icon:"/icon/hp.webp", icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "%", unit: "%",
baseStat: "HP" baseStat: "HP"
}, },
"AttackAddedRatio": { "AttackAddedRatio": {
name:"ATK", name:"ATK",
icon:"/icon/attack.webp", icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "%", unit: "%",
baseStat: "ATK" baseStat: "ATK"
}, },
"DefenceDelta": { "DefenceDelta": {
name:"DEF", name:"DEF",
icon:"/icon/defence.webp", icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "", unit: "",
baseStat: "DEF" baseStat: "DEF"
}, },
"DefenceAddedRatio": { "DefenceAddedRatio": {
name:"DEF", name:"DEF",
icon:"/icon/defence.webp", icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "%", unit: "%",
baseStat: "DEF" baseStat: "DEF"
}, },
"SpeedAddedRatio": { "SpeedAddedRatio": {
name:"SPD", name:"SPD",
icon:"/icon/speed.webp", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "%", unit: "%",
baseStat: "SPD" baseStat: "SPD"
}, },
"BaseSpeed": { "BaseSpeed": {
name:"SPD", name:"SPD",
icon:"/icon/speed.webp", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
baseStat: "SPD" baseStat: "SPD"
}, },
"CriticalChanceBase": { "CriticalChanceBase": {
name:"CRIT Rate", name:"CRIT Rate",
icon:"/icon/crit-rate.webp", icon:"spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%", unit: "%",
baseStat: "CRITRate" baseStat: "CRITRate"
}, },
"CriticalDamageBase": { "CriticalDamageBase": {
name:"CRIT DMG", name:"CRIT DMG",
icon:"/icon/crit-damage.webp", icon:"spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%", unit: "%",
baseStat: "CRITDmg" baseStat: "CRITDmg"
}, },
"HealRatioBase": { "HealRatioBase": {
name:"Outgoing Healing Boost", name:"Outgoing Healing Boost",
icon:"/icon/healing-boost.webp", icon:"spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%", unit: "%",
baseStat: "HealBoost" baseStat: "HealBoost"
}, },
"StatusProbabilityBase": { "StatusProbabilityBase": {
name:"Effect Hit Rate", name:"Effect Hit Rate",
icon:"/icon/effect-hit-rate.webp", icon:"spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%", unit: "%",
baseStat: "EffectHitRate" baseStat: "EffectHitRate"
}, },
"StatusResistanceBase": { "StatusResistanceBase": {
name:"Effect RES", name:"Effect RES",
icon:"/icon/effect-res.webp", icon:"spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%", unit: "%",
baseStat: "EffectRES" baseStat: "EffectRES"
}, },
"BreakDamageAddedRatioBase": { "BreakDamageAddedRatioBase": {
name:"Break Effect", name:"Break Effect",
icon:"/icon/break-effect.webp", icon:"spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%", unit: "%",
baseStat: "BreakEffect" baseStat: "BreakEffect"
}, },
"SpeedDelta": { "SpeedDelta": {
name:"SPD", name:"SPD",
icon:"/icon/speed.webp", icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "", unit: "",
baseStat: "SPD" baseStat: "SPD"
}, },
"PhysicalAddedRatio": { "PhysicalAddedRatio": {
name:"Physical DMG Boost", name:"Physical DMG Boost",
icon:"/icon/physical-add.webp", icon:"spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%", unit: "%",
baseStat: "PhysicalAdd" baseStat: "PhysicalAdd"
}, },
"FireAddedRatio": { "FireAddedRatio": {
name:"Fire DMG Boost", name:"Fire DMG Boost",
icon:"/icon/fire-add.webp", icon:"spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%", unit: "%",
baseStat: "FireAdd" baseStat: "FireAdd"
}, },
"IceAddedRatio": { "IceAddedRatio": {
name:"Ice DMG Boost", name:"Ice DMG Boost",
icon:"/icon/ice-add.webp", icon:"spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%", unit: "%",
baseStat: "IceAdd" baseStat: "IceAdd"
}, },
"ThunderAddedRatio": { "ThunderAddedRatio": {
name:"Thunder DMG Boost", name:"Thunder DMG Boost",
icon:"/icon/thunder-add.webp", icon:"spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%", unit: "%",
baseStat: "ThunderAdd" baseStat: "ThunderAdd"
}, },
"WindAddedRatio": { "WindAddedRatio": {
name:"Wind DMG Boost", name:"Wind DMG Boost",
icon:"/icon/wind-add.webp", icon:"spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%", unit: "%",
baseStat: "WindAdd" baseStat: "WindAdd"
}, },
"QuantumAddedRatio": { "QuantumAddedRatio": {
name:"Quantum DMG Boost", name:"Quantum DMG Boost",
icon:"/icon/quantum-add.webp", icon:"spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%", unit: "%",
baseStat: "QuantumAdd" baseStat: "QuantumAdd"
}, },
"ImaginaryAddedRatio": { "ImaginaryAddedRatio": {
name:"Imaginary DMG Boost", name:"Imaginary DMG Boost",
icon:"/icon/imaginary-add.webp", icon:"spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%", unit: "%",
baseStat: "ImaginaryAdd" baseStat: "ImaginaryAdd"
}, },
"ElationDamageAddedRatioBase": { "ElationDamageAddedRatioBase": {
name:"Elation DMG Boost", name:"Elation DMG Boost",
icon:"/icon/IconJoy.webp", icon:"spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%", unit: "%",
baseStat: "ElationAdd" baseStat: "ElationAdd"
}, },
"SPRatioBase": { "SPRatioBase": {
name:"Energy Regeneration Rate", name:"Energy Regeneration Rate",
icon:"/icon/energy-rate.webp", icon:"spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%", unit: "%",
baseStat: "EnergyRate" baseStat: "EnergyRate"
} }
@@ -169,7 +169,14 @@ export const ratioStats = [
"SpeedAddedRatio", "SpeedAddedRatio",
] ]
export const mappingRelicSlot: Record<string, string> = {
"1": "HEAD",
"2": "HAND",
"3": "BODY",
"4": "FOOT",
"5": "NECK",
"6": "OBJECT",
}
export const themeColors: Record<string, { bg: string; bgHover: string; text: string; border: string }> = { export const themeColors: Record<string, { bg: string; bgHover: string; text: string; border: string }> = {
winter: { winter: {

View File

@@ -1,5 +1,5 @@
import { mappingStats, ratioStats } from "@/constant/constant" import { mappingStats, ratioStats } from "@/constant/constant"
import { AffixDetail } from "@/types" import { EliteData, HardLevelData, MainAffixData, MonsterDetail, SubAffixData} from "@/types"
export function calcPromotion(level: number) { export function calcPromotion(level: number) {
if (level < 20) { if (level < 20) {
@@ -37,29 +37,29 @@ export function calcRarity(rarity: string) {
return 1 return 1
} }
export function calcMainAffixBonus(affix?: AffixDetail, level?: number) { export function calcMainAffixBonus(affix?: MainAffixData, level?: number) {
if (!affix || typeof level !== "number") return "0" if (!affix || typeof level !== "number") return "0"
const value = affix.base + affix.step * level; const value = affix.BaseValue + affix.LevelAdd * level;
if (mappingStats?.[affix.property].unit === "%") { if (mappingStats?.[affix.Property].unit === "%") {
return (value * 100).toFixed(1); return (value * 100).toFixed(1);
} }
if (mappingStats?.[affix.property].name === "SPD") { if (mappingStats?.[affix.Property].name === "SPD") {
return value.toFixed(1); return value.toFixed(1);
} }
return value.toFixed(0); return value.toFixed(0);
} }
export const calcAffixBonus = (affix?: AffixDetail, stepCount?: number, rollCount?: number) => { export const calcAffixBonus = (affix?: SubAffixData, stepCount?: number, rollCount?: number) => {
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number") return "0" if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number") return "0"
if (mappingStats?.[affix.property].unit === "%") { if (mappingStats?.[affix.Property].unit === "%") {
return ((affix.base * rollCount + affix.step * stepCount) * 100).toFixed(1); return ((affix.BaseValue * rollCount + affix.StepValue * stepCount) * 100).toFixed(1);
} }
if (mappingStats?.[affix.property].name === "SPD") { if (mappingStats?.[affix.Property].name === "SPD") {
return (affix.base * rollCount + affix.step * stepCount).toFixed(1); return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(1);
} }
return (affix.base * rollCount + affix.step * stepCount).toFixed(0); return (affix.BaseValue * rollCount + affix.StepValue * stepCount).toFixed(0);
} }
export const calcBaseStat = (baseStat: number, stepStat: number, roundFixed: number, level: number) => { export const calcBaseStat = (baseStat: number, stepStat: number, roundFixed: number, level: number) => {
@@ -72,19 +72,19 @@ export const calcBaseStatRaw = (baseStat?: number, stepStat?: number, level?: nu
return baseStat + stepStat * (level-1); return baseStat + stepStat * (level-1);
} }
export const calcSubAffixBonusRaw = (affix?: AffixDetail, stepCount?: number, rollCount?: number, baseStat?: number) => { export const calcSubAffixBonusRaw = (affix?: SubAffixData, stepCount?: number, rollCount?: number, baseStat?: number) => {
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number" || typeof baseStat !== "number") return 0 if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number" || typeof baseStat !== "number") return 0
if (ratioStats.includes(affix.property)) { if (ratioStats.includes(affix.Property)) {
return (affix.base * rollCount + affix.step * stepCount) * baseStat; return (affix.BaseValue * rollCount + affix.StepValue * stepCount) * baseStat;
} }
return affix.base * rollCount + affix.step * stepCount; return affix.BaseValue * rollCount + affix.StepValue * stepCount;
} }
export const calcMainAffixBonusRaw = (affix?: AffixDetail, level?: number, baseStat?: number) => { export const calcMainAffixBonusRaw = (affix?: MainAffixData, level?: number, baseStat?: number) => {
if (!affix || typeof level !== "number" || typeof baseStat !== "number") return 0 if (!affix || typeof level !== "number" || typeof baseStat !== "number") return 0
const value = affix.base + affix.step * level; const value = affix.BaseValue + affix.LevelAdd * level;
if (ratioStats.includes(affix.property)) { if (ratioStats.includes(affix.Property)) {
return baseStat * value return baseStat * value
} }
@@ -97,4 +97,42 @@ export const calcBonusStatRaw = (affix?: string, baseStat?: number, bonusValue?:
return baseStat * bonusValue return baseStat * bonusValue
} }
return bonusValue return bonusValue
}
export const calcMonsterStats = (
monster: MonsterDetail,
eliteGroup: number,
hardLevelGroup: number,
level: number,
hardLevelConfig: Record<string, Record<string, HardLevelData>>,
eliteConfig: Record<string, EliteData>
) => {
let hardLevelRatio = {
AttackRatio: 1,
DefenceRatio:1,
HPRatio: 1,
SpeedRatio: 1,
StanceRatio: 1
}
if (hardLevelConfig?.[hardLevelGroup.toString()]?.[level.toString()]) {
hardLevelRatio = hardLevelConfig?.[hardLevelGroup.toString()]?.[level.toString()]
}
let eliteRatio = {
AttackRatio: 1,
DefenceRatio:1,
HPRatio: 1,
SpeedRatio: 1,
StanceRatio: 1
}
if (eliteConfig?.[eliteGroup.toString()]) {
eliteRatio = eliteConfig?.[eliteGroup.toString()]
}
return {
atk: monster.Base.AttackBase * monster.Modify.AttackModifyRatio * hardLevelRatio.AttackRatio * eliteRatio.AttackRatio,
def: monster.Base.DefenceBase * monster.Modify.DefenceModifyRatio * hardLevelRatio.DefenceRatio * eliteRatio.DefenceRatio,
hp: monster.Base.HPBase * monster.Modify.HPModifyRatio * hardLevelRatio.HPRatio * eliteRatio.HPRatio,
spd: monster.Base.SpeedBase * monster.Modify.SpeedModifyRatio * hardLevelRatio.SpeedRatio * eliteRatio.SpeedRatio,
stance: (monster.Base.StanceBase * monster.Modify.StanceModifyRatio * hardLevelRatio.StanceRatio * eliteRatio.StanceRatio) / 3,
}
} }

View File

@@ -1,4 +1,4 @@
import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, CharacterDetail, FreeSRJson, RelicStore } from "@/types"; import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, AvatarDetail, FreeSRJson, RelicStore } from "@/types";
function safeNumber(val: string | number | null, fallback = 0): number { function safeNumber(val: string | number | null, fallback = 0): number {
if (!val) return fallback; if (!val) return fallback;
@@ -6,7 +6,7 @@ function safeNumber(val: string | number | null, fallback = 0): number {
return Number.isFinite(num) && num !== 0 ? num : fallback; return Number.isFinite(num) && num !== 0 ? num : fallback;
} }
export function converterToAvatarStore(data: Record<string, CharacterDetail>): { [key: string]: AvatarStore } { export function converterToAvatarStore(data: Record<string, AvatarDetail>): { [key: string]: AvatarStore } {
return Object.fromEntries( return Object.fromEntries(
Object.entries(data).map(([key, value]) => [ Object.entries(data).map(([key, value]) => [
key, key,

View File

@@ -1,4 +1,5 @@
import useMazeStore from "@/stores/mazeStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types"; import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types";
@@ -11,7 +12,7 @@ export function converterToFreeSRJson(
ce_config: CEConfigStore, ce_config: CEConfigStore,
peak_config: PEAKConfigStore, peak_config: PEAKConfigStore,
): FreeSRJson { ): FreeSRJson {
const { SkillTree } = useMazeStore.getState() const { skillConfig } = useDetailDataStore.getState()
const lightcones: LightconeJson[] = [] const lightcones: LightconeJson[] = []
const relics: RelicJson[] = [] const relics: RelicJson[] = []
let battleJson: BattleConfigJson let battleJson: BattleConfigJson
@@ -84,8 +85,8 @@ export function converterToFreeSRJson(
Object.entries(avatars).forEach(([avatarId, avatar]) => { Object.entries(avatars).forEach(([avatarId, avatar]) => {
const skillsByAnchorType: Record<string, number> = {} const skillsByAnchorType: Record<string, number> = {}
for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) { for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) {
if (SkillTree?.[skillId]) { if (skillConfig?.[skillId]) {
skillsByAnchorType[SkillTree[skillId].index_slot] = level > SkillTree[skillId].max_level ? SkillTree[skillId].max_level : level skillsByAnchorType[skillConfig[skillId].IndexSlot] = level > skillConfig[skillId].MaxLevel ? skillConfig[skillId].MaxLevel : level
} }
} }
avatarsJson[avatarId] = { avatarsJson[avatarId] = {

View File

@@ -1,5 +1,5 @@
import { listCurrentLanguage } from "@/constant/constant"; import { listCurrentLanguage } from "@/constant/constant";
import { CharacterBasic, EventBasic, LightConeBasic, MonsterBasic } from "@/types"; import { AvatarDetail } from "@/types";
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
type TFunc = ReturnType<typeof useTranslations> type TFunc = ReturnType<typeof useTranslations>
@@ -7,7 +7,7 @@ type TFunc = ReturnType<typeof useTranslations>
export function getNameChar( export function getNameChar(
locale: string, locale: string,
t: TFunc, t: TFunc,
data: CharacterBasic | undefined data: AvatarDetail | undefined
): string { ): string {
if (!data) return ""; if (!data) return "";
@@ -17,20 +17,20 @@ export function getNameChar(
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
let text = data.lang[langKey] ?? ""; let text = data.Name[langKey] ?? "";
if (!text) { if (!text) {
text = data.lang["en"] ?? ""; text = data.Name["en"] ?? "";
} }
if (Number(data.id) > 8000) { if (data.ID > 8000) {
text = `${t("trailblazer")}${t(data?.baseType?.toLowerCase() ?? "")}`; text = `${t("trailblazer")}${t(data?.BaseType?.toLowerCase() ?? "")}`;
} }
return text; return text;
} }
export function getLocaleName(locale: string, data: LightConeBasic | EventBasic | MonsterBasic | undefined): string { export function getLocaleName(locale: string, data: Record<string, string> | undefined | null): string {
if (!data) { if (!data) {
return "" return ""
} }
@@ -41,10 +41,10 @@ export function getLocaleName(locale: string, data: LightConeBasic | EventBasic
const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase(); const langKey = listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase();
let text = data.lang[langKey] ?? ""; let text = data[langKey] ?? "";
if (!text) { if (!text) {
text = data.lang["en"] ?? ""; text = data["en"] ?? "";
} }
return text return text
} }

View File

@@ -1,18 +1,18 @@
import useAvatarStore from "@/stores/avatarStore"; import { AvatarDetail } from "@/types";
export function getSkillTree(enhanced: string) {
const { avatarSelected, mapAvatarInfo } = useAvatarStore.getState()
export function getSkillTree(avatarSelected: AvatarDetail | null, enhanced: string) {
if (!avatarSelected) return null; if (!avatarSelected) return null;
if (enhanced != "") return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.Enhanced[enhanced].SkillTrees || {}).reduce((acc, dataPointEntry) => { if (enhanced != "" && !!avatarSelected?.Enhanced?.[enhanced]?.SkillTrees) {
const firstEntry = Object.values(dataPointEntry)[0]; return Object.values(avatarSelected?.Enhanced?.[enhanced]?.SkillTrees).reduce((acc, dataPointEntry) => {
if (firstEntry) { const firstEntry = Object.values(dataPointEntry)[0];
acc[firstEntry.PointID] = firstEntry.MaxLevel; if (firstEntry) {
} acc[firstEntry.PointID] = firstEntry.MaxLevel;
return acc; }
}, {} as Record<string, number>) return acc;
}, {} as Record<string, number>)
}
return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.SkillTrees).reduce((acc, dataPointEntry) => { return Object.values(avatarSelected?.SkillTrees).reduce((acc, dataPointEntry) => {
const firstEntry = Object.values(dataPointEntry)[0]; const firstEntry = Object.values(dataPointEntry)[0];
if (firstEntry) { if (firstEntry) {
acc[firstEntry.PointID] = firstEntry.MaxLevel; acc[firstEntry.PointID] = firstEntry.MaxLevel;

View File

@@ -1,84 +1,41 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { AffixDetail, ASDetail, ChangelogItemType, CharacterBasic, CharacterDetail, ConfigMaze, EventBasic, FreeSRJson, LightConeBasic, LightConeDetail, MocDetail, MonsterBasic, PeakDetail, PFDetail, PSResponse, RelicDetail } from "@/types"; import { ASGroupDetail, ChangelogItemType, AvatarDetail, FreeSRJson, LightConeDetail, MOCGroupDetail, MonsterDetail, PeakGroupDetail, PFGroupDetail, PSResponse, RelicSetDetail } from "@/types";
import axios from 'axios'; import axios from 'axios';
import { psResponseSchema } from "@/zod"; import { psResponseSchema } from "@/zod";
import { ExtraData } from "@/types"; import { ExtraData, Metadata } from "@/types";
export async function getConfigMazeApi(): Promise<ConfigMaze> { export async function getMetadataApi(): Promise<Metadata> {
try { try {
const res = await axios.get<ConfigMaze>(`/data/config_maze.json`); const res = await axios.get<Metadata>(`/api/data/metadata`);
return res.data as ConfigMaze; return res.data as Metadata;
} catch (error: unknown) { } catch (error: unknown) {
if (axios.isAxiosError(error)) { console.error('Failed to fetch metadata:', error);
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return { return {
Avatar: {}, BaseType: {},
MOC: {}, DamageType: {},
AS: {}, MainAffix: {},
PF: {}, SubAffix: {},
SkillConfig: {},
Stage: {}, Stage: {},
Skill: {} HardLevelConfig: {},
EliteConfig: {}
}; };
} }
} }
export async function getMainAffixApi(): Promise<Record<string, Record<string, AffixDetail>>> { export async function getAvatarListApi(): Promise<Record<string, AvatarDetail>> {
try { try {
const res = await axios.get<Record<string, Record<string, AffixDetail>>>(`/data/main_affixes.json`); const res = await axios.get<Record<string, AvatarDetail>>(`/api/data/avatar`);
return res.data as Record<string, Record<string, AffixDetail>>;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return {};
}
}
export async function getSubAffixApi(): Promise<Record<string, Record<string, AffixDetail>>> {
try {
const res = await axios.get<Record<string, Record<string, AffixDetail>>>(`/data/sub_affixes.json`);
return res.data as Record<string, Record<string, AffixDetail>>;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return {};
}
}
export async function fetchCharactersApi(locale: string): Promise<Record<string, CharacterDetail>> {
try {
const res = await axios.get<Record<string, CharacterDetail>>(`/data/characters.${locale}.json`);
const resIcon = await axios.get<Record<string, string[]>>(`/data/rank_icon.json`);
for (const [key, char] of Object.entries(res.data)) {
if (resIcon.data[key]) {
char.RankIcon = resIcon.data[key];
}
}
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch characters:', error); console.error('Failed to fetch Avatars:', error);
return {}; return {};
} }
} }
export async function fetchLightconesApi(locale: string): Promise<Record<string, LightConeDetail>> { export async function getLightconeListApi(): Promise<Record<string, LightConeDetail>> {
try { try {
const res = await axios.get<Record<string, LightConeDetail>>(`/data/lightcones.${locale}.json`); const res = await axios.get<Record<string, LightConeDetail>>(`/api/data/lightcone`);
const resBonus = await axios.get<Record<string, Record<string, { type: string, value: number }[]>>>('/data/lightcone_bonus.json');
for (const [key, relic] of Object.entries(res.data)) {
if (resBonus.data[key]) {
relic.Bonus = resBonus.data[key];
}
}
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch lightcones:', error); console.error('Failed to fetch lightcones:', error);
@@ -86,15 +43,9 @@ export async function fetchLightconesApi(locale: string): Promise<Record<string,
} }
} }
export async function fetchRelicsApi(locale: string): Promise<Record<string, RelicDetail>> { export async function getRelicSetListApi(): Promise<Record<string, RelicSetDetail>> {
try { try {
const res = await axios.get<Record<string, RelicDetail>>(`/data/relics.${locale}.json`); const res = await axios.get<Record<string, RelicSetDetail>>(`/api/data/relic`);
const resBonus = await axios.get<Record<string, Record<string, { type: string, value: number }[]>>>('/data/relic_bonus.json');
for (const [key, relic] of Object.entries(res.data)) {
if (resBonus.data[key]) {
relic.Bonus = resBonus.data[key];
}
}
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch relics:', error); console.error('Failed to fetch relics:', error);
@@ -102,130 +53,66 @@ export async function fetchRelicsApi(locale: string): Promise<Record<string, Rel
} }
} }
export async function fetchASEventApi(locale: string): Promise<Record<string, ASDetail> | null> { export async function getMonsterListApi(): Promise<Record<string, MonsterDetail>> {
try { try {
const res = await axios.get<Record<string, ASDetail>>(`/data/as.${locale}.json`); const res = await axios.get<Record<string, MonsterDetail>>(`/api/data/monster`);
return res.data;
} catch (error) {
console.error('Failed to fetch AS:', error);
return null;
}
}
export async function fetchPFEventApi(locale: string): Promise<Record<string, PFDetail> | null> {
try {
const res = await axios.get<Record<string, PFDetail>>(`/data/pf.${locale}.json`);
return res.data;
} catch (error) {
console.error('Failed to fetch PF:', error);
return null;
}
}
export async function fetchMOCEventApi(locale: string): Promise<Record<string, MocDetail[]> | null> {
try {
const res = await axios.get<Record<string, MocDetail[]>>(`/data/moc.${locale}.json`);
return res.data;
} catch (error) {
console.error('Failed to fetch MOC:', error);
return null;
}
}
export async function fetchPeakEventApi(locale: string): Promise<Record<string, PeakDetail> | null> {
try {
const res = await axios.get<Record<string, PeakDetail>>(`/data/peak.${locale}.json`);
return res.data;
} catch (error) {
console.error('Failed to fetch peak:', error);
return null;
}
}
export async function fetchChangelog(): Promise<ChangelogItemType[] | null> {
try {
const res = await axios.get<ChangelogItemType[]>(`/data/changelog.json`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch monster:', error); console.error('Failed to fetch monster:', error);
return null; return {};
} }
} }
export async function getCharacterListApi(): Promise<CharacterBasic[]> { export async function getASEventListApi(): Promise<Record<string, ASGroupDetail>> {
try { try {
const res = await axios.get<CharacterBasic[]>('/data/character.json'); const res = await axios.get<Record<string, ASGroupDetail>>(`/api/data/as`);
return res.data; return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch character list:', error); console.error('Failed to fetch AS:', error);
return []; return {};
} }
} }
export async function getPFEventListApi(): Promise<Record<string, PFGroupDetail>> {
export async function getLightconeListApi(): Promise<LightConeBasic[]> {
try { try {
const res = await axios.get<LightConeBasic[]>('/data/lightcone.json'); const res = await axios.get<Record<string, PFGroupDetail>>(`/api/data/pf`);
return res.data return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch lightcone list:', error); console.error('Failed to fetch PF:', error);
return []; return {};
} }
} }
export async function getMOCEventListApi(): Promise<Record<string, MOCGroupDetail>> {
export async function getMOCEventListApi(): Promise<EventBasic[]> {
try { try {
const res = await axios.get<EventBasic[]>('/data/moc.json'); const res = await axios.get<Record<string, MOCGroupDetail>>(`/api/data/moc`);
return res.data return res.data;
} catch (error) { } catch (error) {
console.error('Failed to fetch moc list:', error); console.error('Failed to fetch MOC:', error);
return []; return {};
} }
} }
export async function getASEventListApi(): Promise<EventBasic[]> { export async function getPeakEventListApi(): Promise<Record<string, PeakGroupDetail>> {
try { try {
const res = await axios.get<EventBasic[]>('/data/as.json'); const res = await axios.get<Record<string, PeakGroupDetail>>(`/api/data/peak`);
return res.data return res.data;
} catch (error: unknown) { } catch (error) {
console.error('Failed to fetch as list:', error); console.error('Failed to fetch peak:', error);
return []; return {};
} }
} }
export async function getPFEventListApi(): Promise<EventBasic[]> { export async function getChangelog(): Promise<ChangelogItemType[]> {
try { try {
const res = await axios.get<EventBasic[]>('/data/pf.json'); const res = await axios.get<ChangelogItemType[]>(`/api/data/changelog`);
return res.data return res.data;
} catch (error: unknown) { } catch (error) {
console.error('Failed to fetch pf list:', error); console.error('Failed to fetch monster:', error);
return []; return [];
} }
} }
export async function getPEAKEventListApi(): Promise<EventBasic[]> {
try {
const res = await axios.get<EventBasic[]>('/data/peak.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch peak list:', error);
return [];
}
}
export async function getMonsterListApi(): Promise<MonsterBasic[]> {
try {
const res = await axios.get<MonsterBasic[]>('/data/monster.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch peak list:', error);
return [];
}
}
export async function SendDataToServer( export async function SendDataToServer(
username: string, username: string,
password: string, password: string,

38
src/lib/cache/cache.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
import { readFileSync, readdirSync } from "fs"
import path from "path"
type CacheItem = {
buf: Uint8Array
type: "json" | "br"
}
const cache = new Map<string, CacheItem>()
const dir = path.join(process.cwd(), "data")
for (const f of readdirSync(dir)) {
const file = path.join(dir, f)
if (f.endsWith(".json.br")) {
const name = f.replace(".json.br", "")
const buf = new Uint8Array(readFileSync(file))
cache.set(name, {
buf,
type: "br"
})
}
if (f.endsWith(".json")) {
const name = f.replace(".json", "")
const buf = new Uint8Array(readFileSync(file))
cache.set(name, {
buf,
type: "json"
})
}
}
export function getDataCache(name: string) {
return cache.get(name)
}

1
src/lib/cache/index.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "./cache"

View File

@@ -1,4 +1,4 @@
export * from "./useFetchConfigData"; export * from "./useFetchMetaData";
export * from "./useFetchAvatarData"; export * from "./useFetchAvatarData";
export * from "./useFetchLightconeData"; export * from "./useFetchLightconeData";
export * from "./useFetchRelicData"; export * from "./useFetchRelicData";

View File

@@ -1,72 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchASEventApi, getASEventListApi } from '@/lib/api' import { getASEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore' import useDetailDataStore from '@/stores/detailDataStore'
import { EventStageDetail } from '@/types'
export const useFetchASData = () => { export const useFetchASGroupData = () => {
const { setASEvent, setMapASInfo } = useEventStore() const { setMapAS } = useDetailDataStore()
const { locale } = useLocaleStore()
const { data: dataAS, error: errorAS } = useQuery({ const query = useQuery({
queryKey: ['asData'], queryKey: ['ASGroupData'],
queryFn: getASEventListApi, queryFn: getASEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
const { data: dataASInfo, error: errorASInfo } = useQuery({
queryKey: ['asInfoData', locale],
queryFn: () =>
fetchASEventApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataAS,
select: (data) => {
const newData = { ...data }
for (const key in newData) {
for (const item of newData[key].Level) {
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map((monster) => {
const { $type, ...rest } = monster;
return rest;
})
}))
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map((monster) => {
const { $type, ...rest } = monster;
return rest;
})
}))
}
}
return newData
},
});
useEffect(() => { useEffect(() => {
if (dataAS && !errorAS) { if (query.data) {
setASEvent(dataAS) setMapAS(query.data)
} else if (errorAS) { } else if (query.error) {
toast.error("Failed to load AS data") toast.error("Failed to load ASGroup data")
} }
}, [dataAS, errorAS, setASEvent]) }, [query.data, query.error, setMapAS])
useEffect(() => { return query
if (dataASInfo && !errorASInfo) {
setMapASInfo(dataASInfo)
} else if (errorASInfo) {
toast.error("Failed to load AS info data")
}
}, [dataASInfo, errorASInfo, setMapASInfo])
} }

View File

@@ -1,63 +1,43 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchCharactersApi, getCharacterListApi } from '@/lib/api' import { getAvatarListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useAvatarStore from '@/stores/avatarStore' import useDetailDataStore from '@/stores/detailDataStore'
import { listCurrentLanguageApi } from '@/constant/constant' import useCurrentDataStore from '@/stores/currentDataStore'
import useLocaleStore from '@/stores/localeStore'
import useUserDataStore from '@/stores/userDataStore' import useUserDataStore from '@/stores/userDataStore'
import { converterToAvatarStore } from '@/helper' import { converterToAvatarStore } from '@/helper'
export const useFetchAvatarData = () => { export const useFetchAvatarData = () => {
const { setAvatar, avatars } = useUserDataStore() const { setMapAvatar, mapAvatar } = useDetailDataStore()
const { setListAvatar, setAllMapAvatarInfo, mapAvatarInfo, setAvatarSelected, avatarSelected } = useAvatarStore() const { setAvatar, avatars } = useUserDataStore()
const { locale } = useLocaleStore() const { avatarSelected, setAvatarSelected } = useCurrentDataStore()
const { data: dataAvatar, error: errorAvatar } = useQuery({
queryKey: ['avatarData'],
queryFn: getCharacterListApi,
staleTime: 1000 * 60 * 5,
})
const query = useQuery({
queryKey: ['AvatarData'],
queryFn: getAvatarListApi,
staleTime: 1000 * 60 * 5,
})
useEffect(() => { useEffect(() => {
const listAvatarId = Object.keys(avatars) const listAvatarId = Object.keys(avatars)
const listAvatarNotExist = Object.entries(mapAvatarInfo).filter(([avatarId]) => !listAvatarId.includes(avatarId)) const listAvatarNotExist = Object.entries(mapAvatar).filter(([avatarId]) => !listAvatarId.includes(avatarId))
const avatarStore = converterToAvatarStore(Object.fromEntries(listAvatarNotExist)) const avatarStore = converterToAvatarStore(Object.fromEntries(listAvatarNotExist))
if (Object.keys(avatarStore).length === 0) return if (Object.keys(avatarStore).length === 0) return
for (const avatar of Object.values(avatarStore)) { for (const avatar of Object.values(avatarStore)) {
setAvatar(avatar) setAvatar(avatar)
} }
}, [mapAvatarInfo]) }, [mapAvatar])
const { data: dataAvatarInfo, error: errorAvatarInfo } = useQuery({
queryKey: ['avatarInfoData', locale],
queryFn: () =>
fetchCharactersApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataAvatar,
});
useEffect(() => { useEffect(() => {
if (dataAvatar && !errorAvatar) { if (query.data) {
setListAvatar(dataAvatar) setMapAvatar(query.data)
if (!avatarSelected) { if (!avatarSelected) {
setAvatarSelected(dataAvatar[0]) setAvatarSelected(Object.values(query.data)[0])
}
} else if (errorAvatar) {
toast.error("Failed to load avatar data")
} }
}, [dataAvatar, errorAvatar]) }
if (query.error) toast.error("Failed to load Avatar data")
}, [query.data, query.error, setMapAvatar, avatarSelected, setAvatarSelected])
useEffect(() => { return query
if (dataAvatarInfo && !errorAvatarInfo) { }
setAllMapAvatarInfo(dataAvatarInfo)
} else if (errorAvatarInfo) {
toast.error("Failed to load avatar info data")
}
}, [dataAvatarInfo, errorAvatarInfo])
}

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchChangelog } from '@/lib/api' import { getChangelog } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useModelStore from '@/stores/modelStore' import useModelStore from '@/stores/modelStore'
@@ -9,23 +9,24 @@ import useLocaleStore from '@/stores/localeStore'
export const useFetchChangelog = () => { export const useFetchChangelog = () => {
const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore() const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore()
const { setIsChangelog } = useModelStore() const { setIsChangelog } = useModelStore()
const { data: dataChangelog, error: errorChangelog } = useQuery({ const query = useQuery({
queryKey: ['changelog'], queryKey: ['changelog'],
queryFn: fetchChangelog, queryFn: getChangelog,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (dataChangelog && !errorChangelog) { if (query.data && !query.error) {
setChangelog(dataChangelog) setChangelog(query.data)
if (dataChangelog?.[0] && dataChangelog[0].version != currentVersion) { if (query.data?.[0] && query.data[0].version != currentVersion) {
setIsChangelog(true) setIsChangelog(true)
setCurrentVersion(dataChangelog[0].version) setCurrentVersion(query.data[0].version)
} }
} else if (errorChangelog) { } else if (query.error) {
toast.error("Failed to load changelog data") toast.error("Failed to load changelog data")
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataChangelog, errorChangelog, setChangelog, setCurrentVersion, setIsChangelog]) }, [query.data, query.error, setChangelog, setCurrentVersion, setIsChangelog])
return query
} }

View File

@@ -1,37 +0,0 @@
"use client"
import { useQuery } from '@tanstack/react-query'
import { getConfigMazeApi, getMainAffixApi, getSubAffixApi } from '@/lib/api'
import useAffixStore from '@/stores/affixStore'
import useMazeStore from '@/stores/mazeStore'
import { useEffect } from 'react'
import { toast } from 'react-toastify'
export const useFetchConfigData = () => {
const { setMapMainAffix, setMapSubAffix } = useAffixStore()
const { setAllMazeData } = useMazeStore()
const { data, error } = useQuery({
queryKey: ['initialConfigData'],
queryFn: async () => {
const [maze, main, sub] = await Promise.all([
getConfigMazeApi(),
getMainAffixApi(),
getSubAffixApi(),
])
return { maze, main, sub }
},
staleTime: 1000 * 60 * 5,
})
useEffect(() => {
if (data && !error) {
setAllMazeData(data.maze)
setMapMainAffix(data.main)
setMapSubAffix(data.sub)
}
else if (error) {
toast.error("Failed to load initial config data")
}
}, [data, error, setAllMazeData, setMapMainAffix, setMapSubAffix])
}

View File

@@ -1,46 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchLightconesApi, getLightconeListApi } from '@/lib/api' import { getLightconeListApi } from '@/lib/api';
import { useEffect } from 'react' import { useEffect } from 'react'
import useLightconeStore from '@/stores/lightconeStore'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useDetailDataStore from '@/stores/detailDataStore';
export const useFetchLightconeData = () => { export const useFetchLightconeData = () => {
const { setListLightcone, setAllMapLightconeInfo } = useLightconeStore() const { setMapLightCone } = useDetailDataStore()
const { locale } = useLocaleStore() const query = useQuery({
const { data: dataLightcone, error: errorLightcone } = useQuery({
queryKey: ['lightconeData'], queryKey: ['lightconeData'],
queryFn: getLightconeListApi, queryFn: getLightconeListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
const { data: dataLightconeInfo, error: errorLightconeInfo } = useQuery({
queryKey: ['lightconeInfoData', locale],
queryFn: () =>
fetchLightconesApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataLightcone,
});
useEffect(() => { useEffect(() => {
if (dataLightcone && !errorLightcone) { if (query.data && !query.error) {
setListLightcone(dataLightcone) setMapLightCone(query.data)
} else if (errorLightcone) { } else if (query.error) {
toast.error("Failed to load lightcone data") toast.error("Failed to load lightcone data")
} }
}, [dataLightcone, errorLightcone, setListLightcone]) }, [query.data, query.error, setMapLightCone])
useEffect(() => { return query
if (dataLightconeInfo && !errorLightconeInfo) {
setAllMapLightconeInfo(dataLightconeInfo)
} else if (errorLightconeInfo) {
toast.error("Failed to load lightcone info data")
}
}, [dataLightconeInfo, errorLightconeInfo, setAllMapLightconeInfo])
} }

View File

@@ -1,66 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchMOCEventApi, getMOCEventListApi } from '@/lib/api' import { getMOCEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant' import useDetailDataStore from '@/stores/detailDataStore'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail, MocDetail } from '@/types'
export const useFetchMOCData = () => { export const useFetchMOCGroupData = () => {
const { setMOCEvent, setMapMOCInfo } = useEventStore() const { setMapMoc } = useDetailDataStore()
const { locale } = useLocaleStore() const query = useQuery({
const { data: dataMOC, error: errorMOC } = useQuery({ queryKey: ['MOCGroupData'],
queryKey: ['mocData'],
queryFn: getMOCEventListApi, queryFn: getMOCEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
const { data: dataMOCInfo, error: errorMOCInfo } = useQuery({
queryKey: ['mocInfoData', locale],
queryFn: async () => {
const result = await fetchMOCEventApi(
listCurrentLanguageApi[locale.toLowerCase()]
);
return result;
},
staleTime: 1000 * 60 * 5,
select: (data) => {
const newData = { ...data }
for (const key in newData) {
for (const item of newData[key]) {
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
}
}
return newData
},
enabled: !!dataMOC,
});
useEffect(() => { useEffect(() => {
if (dataMOC && !errorMOC) { if (query.data && !query.error) {
setMOCEvent(dataMOC) setMapMoc(query.data)
} else if (errorMOC) { } else if (query.error) {
toast.error("Failed to load MOC data") toast.error("Failed to load MOCGroup data")
} }
}, [dataMOC, errorMOC, setMOCEvent]) }, [query.data, query.error, setMapMoc])
useEffect(() => { return query
if (dataMOCInfo && !errorMOCInfo) {
setMapMOCInfo(dataMOCInfo as Record<string, MocDetail[]>)
} else if (errorMOCInfo) {
toast.error("Failed to load MOC info data")
}
}, [dataMOCInfo, errorMOCInfo, setMapMOCInfo])
} }

View File

@@ -0,0 +1,33 @@
"use client"
import { useQuery } from '@tanstack/react-query'
import { useEffect } from 'react'
import { toast } from 'react-toastify'
import { getMetadataApi } from '@/lib/api'
import useCurrentDataStore from '@/stores/currentDataStore'
import useDetailDataStore from '@/stores/detailDataStore'
export const useFetchConfigData = () => {
const { setMetaData } = useDetailDataStore()
const { setSettingData } = useCurrentDataStore()
const query = useQuery({
queryKey: ['initialConfigData'],
queryFn: async () => {
const metaData = await getMetadataApi()
return { metaData }
},
staleTime: 1000 * 60 * 5,
})
useEffect(() => {
if (query.data && !query.error) {
setSettingData(query.data.metaData)
setMetaData(query.data.metaData)
}
else if (query.error) {
toast.error("Failed to load initial config data")
}
}, [query.data, query.error, setMetaData, setSettingData])
return query
}

View File

@@ -2,28 +2,24 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getMonsterListApi } from '@/lib/api' import { getMonsterListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useMonsterStore from '@/stores/monsterStore'
import { MonsterBasic } from '@/types'
export const useFetchMonsterData = () => { export const useFetchMonsterData = () => {
const { setAllMapMonster, setListMonster } = useMonsterStore() const { setMapMonster } = useDetailDataStore()
const { data: dataMonster, error: errorMonster } = useQuery({ const query = useQuery({
queryKey: ['monsterData'], queryKey: ['MonsterData'],
queryFn: getMonsterListApi, queryFn: getMonsterListApi,
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
useEffect(() => { useEffect(() => {
if (dataMonster && !errorMonster) { if (query.data && !query.error) {
setListMonster(dataMonster.sort((a, b) => Number(b.id) - Number(a.id))) setMapMonster(query.data)
const monsterMap = dataMonster.reduce<Record<string, MonsterBasic>>((acc, m) => { } else if (query.error) {
acc[m.id] = m toast.error("Failed to load Monster data")
return acc
}, {})
setAllMapMonster(monsterMap)
} else if (errorMonster) {
toast.error("Failed to load monster data")
} }
}, [dataMonster, errorMonster, setAllMapMonster, setListMonster]) }, [query.data, query.error, setMapMonster])
return query
} }

View File

@@ -1,68 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchPeakEventApi, getPEAKEventListApi } from '@/lib/api' import { getPeakEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant' import useDetailDataStore from '@/stores/detailDataStore'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail, PeakDetail } from '@/types'
export const useFetchPEAKData = () => { export const useFetchPeakGroupData = () => {
const { setPEAKEvent, setMapPEAKInfo } = useEventStore() const { setMapPeak } = useDetailDataStore()
const { locale } = useLocaleStore() const query = useQuery({
const { data: dataPEAK, error: errorPEAK } = useQuery({ queryKey: ['PeakGroupData'],
queryKey: ['peakData'], queryFn: getPeakEventListApi,
queryFn: getPEAKEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
const { data: dataPEAKInfo, error: errorPEAKInfo } = useQuery({
queryKey: ['peakInfoData', locale],
queryFn: () =>
fetchPeakEventApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
select: (data) => {
const newData = { ...data }
for (const key in newData) {
for (const item of newData[key].PreLevel) {
item.EventIDList = item.EventIDList.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
}
newData[key].BossLevel.EventIDList = newData[key].BossLevel.EventIDList.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
newData[key].BossConfig.EventIDList = newData[key].BossConfig.EventIDList.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
}
return newData
},
enabled: !!dataPEAK,
});
useEffect(() => { useEffect(() => {
if (dataPEAK && !errorPEAK) { if (query.data && !query.error) {
setPEAKEvent(dataPEAK) setMapPeak(query.data)
} else if (errorPEAK) { } else if (query.error) {
toast.error("Failed to load PEAK data") toast.error("Failed to load PeakGroup data")
} }
}, [dataPEAK, errorPEAK, setPEAKEvent]) }, [query.data, query.error, setMapPeak])
useEffect(() => { return query
if (dataPEAKInfo && !errorPEAKInfo) {
setMapPEAKInfo(dataPEAKInfo as Record<string, PeakDetail>)
} else if (errorPEAKInfo) {
toast.error("Failed to load PEAK info data")
}
}, [dataPEAKInfo, errorPEAKInfo, setMapPEAKInfo])
} }

View File

@@ -1,64 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchPFEventApi, getPFEventListApi } from '@/lib/api' import { getPFEventListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant' import useDetailDataStore from '@/stores/detailDataStore'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail } from '@/types'
export const useFetchPFData = () => { export const useFetchPFGroupData = () => {
const { setPFEvent, setMapPFInfo } = useEventStore() const { setMapPF } = useDetailDataStore()
const { locale } = useLocaleStore() const query = useQuery({
const { data: dataPF, error: errorPF } = useQuery({ queryKey: ['PFGroupData'],
queryKey: ['pfData'],
queryFn: getPFEventListApi, queryFn: getPFEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}) })
const { data: dataPFInfo, error: errorPFInfo } = useQuery({
queryKey: ['pfInfoData', locale],
queryFn: () =>
fetchPFEventApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5,
enabled: !!dataPF,
select: (data) => {
const newData = { ...data }
for (const key in newData) {
for (const item of newData[key].Level) {
item.EventIDList1 = item.EventIDList1.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
item.EventIDList2 = item.EventIDList2.map((event: EventStageDetail) => ({
...event,
MonsterList: event.MonsterList.map(({ $type, ...rest }) => rest)
}))
}
}
return newData
},
});
useEffect(() => { useEffect(() => {
if (dataPF && !errorPF) { if (query.data && !query.error) {
setPFEvent(dataPF) setMapPF(query.data)
} else if (errorPF) { } else if (query.error) {
toast.error("Failed to load PF data") toast.error("Failed to load PFGroup data")
} }
}, [dataPF, errorPF, setPFEvent]) }, [query.data, query.error, setMapPF])
useEffect(() => { return query
if (dataPFInfo && !errorPFInfo) {
setMapPFInfo(dataPFInfo)
} else if (errorPFInfo) {
toast.error("Failed to load PF info data")
}
}, [dataPFInfo, errorPFInfo, setMapPFInfo])
} }

View File

@@ -1,31 +1,25 @@
"use client" "use client"
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchRelicsApi } from '@/lib/api' import { getRelicSetListApi } from '@/lib/api'
import { useEffect } from 'react' import { useEffect } from 'react'
import useRelicStore from '@/stores/relicStore' import useDetailDataStore from '@/stores/detailDataStore'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
export const useFetchRelicData = () => { export const useFetchRelicSetData = () => {
const { setAllMapRelicInfo } = useRelicStore() const { setMapRelicSet } = useDetailDataStore()
const { locale } = useLocaleStore() const query = useQuery({
queryKey: ['RelicSetData'],
const { data: dataRelicInfo, error: errorRelicInfo } = useQuery({ queryFn: getRelicSetListApi,
queryKey: ['relicInfoData', locale],
queryFn: () =>
fetchRelicsApi(
listCurrentLanguageApi[locale.toLowerCase()]
),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
}); })
useEffect(() => { useEffect(() => {
if (dataRelicInfo && !errorRelicInfo) { if (query.data && !query.error) {
setAllMapRelicInfo(dataRelicInfo) setMapRelicSet(query.data)
} else if (errorRelicInfo) { } else if (query.error) {
toast.error("Failed to load relic info data") toast.error("Failed to load RelicSet data")
} }
}, [query.data, query.error, setMapRelicSet])
}, [dataRelicInfo, errorRelicInfo, setAllMapRelicInfo])
return query
} }

View File

@@ -1,18 +0,0 @@
import { AffixDetail } from '@/types';
import { create } from 'zustand'
interface AffixState {
mapMainAffix: Record<string, Record<string, AffixDetail>>;
mapSubAffix: Record<string, Record<string, AffixDetail>>;
setMapMainAffix: (newMainAffix: Record<string, Record<string, AffixDetail>>) => void;
setMapSubAffix: (newSubAffix: Record<string, Record<string, AffixDetail>>) => void;
}
const useAffixStore = create<AffixState>((set) => ({
mapMainAffix: {},
mapSubAffix: {},
setMapMainAffix: (newMainAffix: Record<string, Record<string, AffixDetail>>) => set({ mapMainAffix: newMainAffix }),
setMapSubAffix: (newSubAffix: Record<string, Record<string, AffixDetail>>) => set({ mapSubAffix: newSubAffix }),
}));
export default useAffixStore;

View File

@@ -1,76 +0,0 @@
import { CharacterBasic, CharacterDetail, FilterAvatarType } from '@/types';
import { create } from 'zustand'
interface AvatarState {
listAvatar: CharacterBasic[];
listRawAvatar: CharacterBasic[];
filter: FilterAvatarType;
avatarSelected: CharacterBasic | null;
mapAvatarInfo: Record<string, CharacterDetail>;
skillSelected: string | null;
listElement: Record<string, boolean>;
listPath: Record<string, boolean>;
setListElement: (newListElement: Record<string, boolean>) => void;
setListPath: (newListPath: Record<string, boolean>) => void;
setListAvatar: (newListAvatar: CharacterBasic[]) => void;
setAvatarSelected: (newAvatarSelected: CharacterBasic) => void;
setFilter: (newFilter: FilterAvatarType) => void;
setMapAvatarInfo: (avatarId: string, newCharacter: CharacterDetail) => void;
setAllMapAvatarInfo: (newCharacter: Record<string, CharacterDetail>) => void;
setSkillSelected: (newSkillSelected: string | null) => void;
}
const useAvatarStore = create<AvatarState>((set, get) => ({
listAvatar: [],
listRawAvatar: [],
filter: {
name: "",
path: [],
element: [],
rarity: [],
locale: "",
},
avatarSelected: null,
skillSelected: null,
mapAvatarInfo: {},
listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false },
listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false, elation: false },
setListElement: (newListElement: Record<string, boolean>) => set({ listElement: newListElement }),
setListPath: (newListPath: Record<string, boolean>) => set({ listPath: newListPath }),
setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }),
setListAvatar: (newListAvatar: CharacterBasic[]) => set({ listAvatar: newListAvatar, listRawAvatar: newListAvatar }),
setAvatarSelected: (newAvatarSelected: CharacterBasic) => set({ avatarSelected: newAvatarSelected }),
setFilter: (newFilter: FilterAvatarType) => {
set({ filter: newFilter })
if (newFilter.locale === "") {
return
}
let filteredList = get().listRawAvatar;
if (newFilter.name) {
filteredList = filteredList.filter((avatar) => {
return avatar.lang?.[newFilter.locale]?.toLowerCase().includes(newFilter.name.toLowerCase()) ?? false;
});
}
if (newFilter.path.length > 0) {
filteredList = filteredList.filter((avatar) => {
return newFilter.path.some((path) => avatar.baseType?.toLowerCase().includes(path.toLowerCase())) ?? false;
});
}
if (newFilter.element.length > 0) {
filteredList = filteredList.filter((avatar) => {
return newFilter.element.some((element) => avatar.damageType?.toLowerCase().includes(element.toLowerCase())) ?? false;
});
}
if (newFilter.rarity.length > 0) {
filteredList = filteredList.filter((avatar) => {
return newFilter.rarity.some((rarity) => avatar.rank?.toLowerCase().includes(rarity.toLowerCase())) ?? false;
});
}
set({ listAvatar: filteredList });
},
setMapAvatarInfo: (avatarId: string, newCharacter: CharacterDetail) => set((state) => ({ mapAvatarInfo: { ...state.mapAvatarInfo, [avatarId]: newCharacter } })),
setAllMapAvatarInfo: (newCharacter: Record<string, CharacterDetail>) => set((state) => ({ mapAvatarInfo: { ...state.mapAvatarInfo, ...newCharacter } })),
}));
export default useAvatarStore;

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