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",
"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>
<ThemeProvider>
<ClientThemeWrapper>
<ClientDataFetcher />
<div className="min-h-screen w-full">
<Header />
<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">
<AvatarBar />
</div>
<div className="col-span-12 sm:col-span-8 lg:col-span-9">
<ActionBar />
{children}
<ClientDataFetcher >
<div className="min-h-screen w-full">
<Header />
<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">
<AvatarBar />
</div>
<div className="col-span-12 sm:col-span-8 lg:col-span-9">
<ActionBar />
{children}
</div>
</div>
<Footer />
</div>
<Footer />
</div>
</ClientDataFetcher>
</ClientThemeWrapper>
</ThemeProvider>
</QueryProviderWrapper>

View File

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

View File

@@ -1,32 +1,57 @@
"use client"
import Image from "next/image"
import { useEffect } from "react"
import { useMemo } from "react"
import CharacterCard from "../card/characterCard"
import useLocaleStore from "@/stores/localeStore"
import useAvatarStore from "@/stores/avatarStore"
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 }) {
const {
listAvatar,
const {
avatarSearch,
mapAvatarElementActive,
mapAvatarPathActive,
setAvatarSearch,
setAvatarSelected,
setSkillSelected,
setFilter,
filter,
listElement,
listPath,
setListElement,
setListPath
} = useAvatarStore()
setMapAvatarElementActive,
setMapAvatarPathActive,
setSkillIDSelected,
} = useCurrentDataStore()
const { mapAvatar, baseType, damageType } = useDetailDataStore()
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore()
const {locale} = useLocaleStore()
const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return []
useEffect(() => {
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
}, [locale, listElement, listPath])
let list = Object.values(mapAvatar)
if (avatarSearch) {
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 (
@@ -39,23 +64,23 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
<input type="text"
placeholder={transI18n("placeholderCharacter")}
className="input input-bordered input-primary w-full"
value={filter.name}
onChange={(e) => setFilter({ ...filter, name: e.target.value, locale: locale })}
value={avatarSearch}
onChange={(e) => setAvatarSearch(e.target.value)}
/>
</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">
{Object.keys(listElement).map((key, index) => (
{Object.entries(damageType).map(([key, value]) => (
<div
key={index}
key={key}
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"
style={{
backgroundColor: listElement[key] ? "#374151" : "#6B7280"
backgroundColor: mapAvatarElementActive[key] ? "#374151" : "#6B7280"
}}>
<Image
src={`/icon/${key}.webp`}
src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key}
unoptimized
crossOrigin="anonymous"
@@ -67,20 +92,20 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
</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]">
{Object.keys(listPath).map((key, index) => (
{Object.entries(baseType).map(([key, value]) => (
<div
key={index}
key={key}
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"
style={{
backgroundColor: listPath[key] ? "#374151" : "#6B7280"
backgroundColor: mapAvatarPathActive[key] ? "#374151" : "#6B7280"
}}
>
<Image
src={`/icon/${key}.webp`}
src={`${process.env.CDN_URL}/${value.Icon}`}
unoptimized
crossOrigin="anonymous"
alt={key}
@@ -98,7 +123,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
{listAvatar.map((item, index) => (
<div key={index} onClick={() => {
setAvatarSelected(item);
setSkillSelected(null)
setSkillIDSelected(null)
if (onClose) onClose()
}}>
<CharacterCard data={item} />

View File

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

View File

@@ -2,18 +2,20 @@
import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { CharacterBasic } from '@/types';
import { AvatarDetail } from '@/types';
import ParseText from '../parseText';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import useDetailDataStore from '@/stores/detailDataStore';
interface CharacterCardProps {
data: CharacterBasic
data: AvatarDetail
}
export default function CharacterCard({ data }: CharacterCardProps) {
const { locale } = useLocaleStore();
const transI18n = useTranslations("DataPage");
const { baseType, damageType } = useDetailDataStore()
return (
<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"
>
<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-purple-400 via-purple-600/70 to-purple-800/50"
}`}
@@ -35,7 +37,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
height={512}
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${data.icon}`}
src={`${process.env.CDN_URL}/${data.Image.AvatarIconPath}`}
priority={true}
className="rounded-md w-full h-full object-contain"
alt="ALT"
@@ -45,18 +47,18 @@ export default function CharacterCard({ data }: CharacterCardProps) {
height={32}
unoptimized
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"
alt={data.damageType.toLowerCase()}
alt={data.DamageType.toLowerCase()}
/>
<Image
width={32}
height={32}
unoptimized
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"
alt={data.baseType.toLowerCase()}
alt={data.BaseType.toLowerCase()}
style={{
boxShadow: "inset 0 0 8px 4px #9CA3AF"
}}

View File

@@ -3,16 +3,14 @@
import React from 'react';
import { CharacterInfoCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore';
import useAvatarStore from '@/stores/avatarStore';
import useLightconeStore from '@/stores/lightconeStore';
import Image from 'next/image';
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 }) {
const isSelected = selectedCharacters.some((selectedCharacter) => selectedCharacter.avatar_id === character.avatar_id);
const { mapAvatarInfo } = useAvatarStore();
const { mapLightconeInfo } = useLightconeStore();
const { mapAvatar, mapLightCone, baseType, damageType } = useDetailDataStore();
const { locale } = useLocaleStore();
return (
@@ -29,8 +27,8 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
<div className="relative mb-4">
<div className="w-full h-48 rounded-lg overflow-hidden relative">
<Image
src={`${process.env.CDN_URL}/spriteoutput/avatarshopicon/avatar/${character.avatar_id}.png`}
alt={mapAvatarInfo[character.avatar_id.toString()]?.Name || ""}
src={`${process.env.CDN_URL}/${mapAvatar?.[character.avatar_id.toString()]?.Image?.AvatarIconPath}`}
alt={getLocaleName(locale, mapAvatar?.[character.avatar_id.toString()]?.Name)}
width={376}
height={512}
unoptimized
@@ -42,18 +40,18 @@ export default function CharacterInfoCard({ character, selectedCharacters, onCha
height={48}
unoptimized
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"
alt={mapAvatarInfo[character.avatar_id.toString()]?.DamageType.toLowerCase()}
alt={mapAvatar[character.avatar_id.toString()]?.DamageType.toLowerCase()}
/>
<Image
width={48}
height={48}
unoptimized
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"
alt={mapAvatarInfo[character.avatar_id.toString()]?.BaseType.toLowerCase()}
alt={mapAvatar[character.avatar_id.toString()]?.BaseType.toLowerCase()}
style={{
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="text-center">
<ParseText className="text-lg font-bold"
text={mapAvatarInfo[character.avatar_id.toString()]?.Name}
text={getLocaleName(locale, mapAvatar[character.avatar_id.toString()]?.Name)}
locale={locale}
/>
<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
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/spriteoutput/lightconemaxfigures/${character.lightcone.item_id}.png`}
alt={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
src={`${process.env.CDN_URL}/${mapLightCone?.[character.lightcone.item_id.toString()]?.Image?.ImagePath}`}
alt={getLocaleName(locale, mapLightCone?.[character.lightcone.item_id.toString()]?.Name)}
width={348}
height={408}
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-lg font-bold">
<ParseText
text={mapLightconeInfo[character.lightcone.item_id.toString()]?.Name}
text={getLocaleName(locale, mapLightCone[character.lightcone.item_id.toString()]?.Name)}
locale={locale}
/>
</div>

View File

@@ -2,27 +2,27 @@
import { getLocaleName } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { LightConeBasic } from '@/types';
import ParseText from '../parseText';
import Image from 'next/image';
import { LightConeDetail } from '@/types';
interface LightconeCardProps {
data: LightConeBasic
data: LightConeDetail
}
export default function LightconeCard({ data }: LightconeCardProps) {
const { locale } = useLocaleStore();
const text = getLocaleName(locale, data)
const text = getLocaleName(locale, data.Name)
return (
<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
hover:scale-105 cursor-pointer min-h-55"
>
<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"
: 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"
}`}
>
@@ -30,7 +30,7 @@ export default function LightconeCard({ data }: LightconeCardProps) {
<div className="relative w-full h-full">
<Image
loading="lazy"
src={`${process.env.CDN_URL}/${data.thumbnail}`}
src={`${process.env.CDN_URL}/${data?.Image?.ThumbnailPath}`}
unoptimized
crossOrigin="anonymous"
width={348}

View File

@@ -3,14 +3,15 @@
import React from 'react';
import { AvatarProfileCardType } from '@/types';
import useLocaleStore from '@/stores/localeStore';
import useLightconeStore from '@/stores/lightconeStore';
import Image from 'next/image';
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 }) {
const isSelected = selectedProfile.some((selectedProfile) => selectedProfile.key === profile.key);
const { mapLightconeInfo } = useLightconeStore();
const { mapLightCone } = useDetailDataStore();
const { locale } = useLocaleStore();
return (
@@ -30,7 +31,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
unoptimized
crossOrigin="anonymous"
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}
height={408}
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-lg font-bold">
<ParseText
text={mapLightconeInfo[profile.lightcone.item_id.toString()]?.Name}
text={getLocaleName(locale, mapLightCone[profile.lightcone.item_id.toString()]?.Name)}
locale={locale}
/>
</div>
@@ -59,7 +60,7 @@ export default function ProfileCard({ profile, selectedProfile, onProfileToggle
<Image
unoptimized
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"
width={124}
height={124}

View File

@@ -1,29 +1,56 @@
"use client";
"use client"
import {
useFetchASData,
useFetchASGroupData,
useFetchAvatarData,
useFetchChangelog,
useFetchConfigData,
useFetchLightconeData,
useFetchMOCData,
useFetchMOCGroupData,
useFetchMonsterData,
useFetchPEAKData,
useFetchPFData,
useFetchRelicData
} from "@/lib/hooks";
useFetchPeakGroupData,
useFetchPFGroupData,
useFetchRelicSetData
} from "@/lib/hooks"
export default function ClientDataFetcher() {
useFetchConfigData();
useFetchAvatarData();
useFetchLightconeData();
useFetchRelicData();
useFetchMonsterData();
useFetchPFData();
useFetchMOCData();
useFetchASData();
useFetchPEAKData();
useFetchChangelog();
export default function ClientDataFetcher({
children
}: {
children: React.ReactNode
}) {
const q1 = useFetchConfigData()
const q2 = useFetchAvatarData()
const q3 = useFetchLightconeData()
const q4 = useFetchRelicSetData()
const q5 = useFetchMonsterData()
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"
import { replaceByParam } from "@/helper";
import useListAvatarStore from "@/stores/avatarStore";
import { replaceByParam, getLocaleName } from '@/helper';
import Image from "next/image";
import ParseText from "../parseText";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import { useMemo } from "react";
import { useTranslations } from "next-intl";
import useCurrentDataStore from "@/stores/currentDataStore";
export default function EidolonsInfo() {
const { avatarSelected, mapAvatarInfo } = useListAvatarStore()
const { avatarSelected } = useCurrentDataStore()
const { locale } = useLocaleStore()
const transI18n = useTranslations("DataPage")
const { setAvatars, avatars } = useUserDataStore()
const charRank = useMemo(() => {
if (!avatarSelected) return null;
const avatar = avatars[avatarSelected.id];
const avatar = avatars[avatarSelected.ID];
if (avatar?.enhanced != "") {
return mapAvatarInfo[avatarSelected.id]?.Enhanced[avatar?.enhanced].Ranks
return avatarSelected?.Enhanced?.[avatar?.enhanced]?.Ranks
}
return mapAvatarInfo[avatarSelected.id]?.Ranks
}, [avatarSelected, avatars, locale, mapAvatarInfo]);
return avatarSelected?.Ranks
}, [avatarSelected, avatars]);
return (
<div className="bg-base-100 rounded-xl p-6 shadow-lg">
@@ -32,22 +31,22 @@ export default function EidolonsInfo() {
{transI18n("eidolons")}
</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">
{charRank && avatars[avatarSelected?.id || ""] && (
{charRank && avatars[avatarSelected?.ID || ""] && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(charRank || {}).map(([key, rank]) => (
<div key={key}
className="flex flex-col items-center cursor-pointer hover:scale-105"
onClick={() => {
let newRank = Number(key)
if (avatars[avatarSelected?.id || ""]?.data?.rank == Number(key)) {
if (avatars[avatarSelected?.ID || ""]?.data?.rank == Number(key)) {
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
className={`w-60 object-contain mb-2 ${Number(key) <= avatars[avatarSelected?.id || ""]?.data?.rank ? "" : "grayscale"}`}
src={`${process.env.CDN_URL}/ui/ui3d/rank/_dependencies/textures/${avatarSelected?.id}/${avatarSelected?.id}_Rank_${key}.png`}
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`}
alt={`Rank ${key}`}
priority
unoptimized
@@ -60,12 +59,12 @@ export default function EidolonsInfo() {
<span className="inline-block text-indigo-500">{key}.</span>
<ParseText
locale={locale}
text={rank.Name}
text={getLocaleName(locale, rank.Name)}
className="text-center text-base font-normal leading-tight"
/>
</div>
<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>
))}

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

View File

@@ -8,7 +8,6 @@ import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import { motion } from "framer-motion";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import EnkaImport from "../importBar/enka";
@@ -564,19 +563,6 @@ export default function Header() {
</ul>
</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>
{modalConfigs?.map(({ id, title, onClose, content }) => (

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,115 +2,117 @@
import { useEffect, useMemo } from "react";
import SelectCustomText from "../select/customSelectText";
import useEventStore from "@/stores/eventStore";
import { getLocaleName, replaceByParam } from "@/helper";
import { calcMonsterStats, getLocaleName, replaceByParam } from "@/helper";
import useLocaleStore from "@/stores/localeStore";
import useUserDataStore from "@/stores/userDataStore";
import useMonsterStore from "@/stores/monsterStore";
import Image from "next/image";
import useMazeStore from "@/stores/mazeStore";
import { useTranslations } from "next-intl";
import { MonsterStore } from "@/types";
import useDetailDataStore from "@/stores/detailDataStore";
export default function MocBar() {
const { MOCEvent, mapMOCInfo } = useEventStore()
const { mapMonster } = useMonsterStore()
const { locale } = useLocaleStore()
const {
moc_config,
setMocConfig
} = useUserDataStore()
const { MOC } = useMazeStore()
const { mapMonster, mapMoc, damageType, hardLevelConfig, eliteConfig } = useDetailDataStore()
const transI18n = useTranslations("DataPage")
const challengeSelected = useMemo(() => {
return mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id)
}, [moc_config, mapMOCInfo])
return mapMoc[moc_config.event_id.toString()]?.Level.find((moc) => moc.ID === moc_config.challenge_id)
}, [moc_config, mapMoc])
const eventSelected = useMemo(() => {
return mapMoc[moc_config.event_id.toString()]
}, [moc_config, mapMoc])
useEffect(() => {
const challenge = mapMOCInfo[moc_config.event_id.toString()]?.find((moc) => moc.Id === moc_config.challenge_id)
if (moc_config.event_id !== 0 && moc_config.challenge_id !== 0 && challenge) {
const newBattleConfig = structuredClone(moc_config)
newBattleConfig.cycle_count = 0
if (moc_config.use_cycle_count) {
newBattleConfig.cycle_count = challenge.Countdown
}
newBattleConfig.blessings = []
if (moc_config.use_turbulence_buff && MOC[moc_config.challenge_id.toString()]) {
if (!challengeSelected || moc_config.event_id === 0 || moc_config.challenge_id === 0) return
const newBattleConfig = structuredClone(moc_config)
newBattleConfig.cycle_count = 0
if (moc_config.use_cycle_count) {
newBattleConfig.cycle_count = challengeSelected.TurnLimit
}
newBattleConfig.blessings = []
if (moc_config.use_turbulence_buff && challengeSelected) {
challengeSelected.MazeBuff.map((item) => {
newBattleConfig.blessings.push({
id: Number(MOC[moc_config.challenge_id.toString()].maze_buff),
id: item.ID,
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
}, [
moc_config.event_id,
moc_config.challenge_id,
moc_config.floor_side,
mapMOCInfo,
MOC,
moc_config.use_cycle_count,
moc_config.use_turbulence_buff,
mapMoc,
])
if (!MOCEvent) return null
if (!mapMoc) return null
return (
<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="mb-4 w-full">
<SelectCustomText
customSet={MOCEvent.map((moc) => ({
id: moc.id,
name: getLocaleName(locale, moc),
time: `${moc.begin} - ${moc.end}`,
customSet={Object.values(mapMoc).sort((a, b) => b.ID - a.ID).map((moc) => ({
id: moc.ID.toString(),
name: getLocaleName(locale, moc.Name),
time: `${moc.BeginTime} - ${moc.EndTime}`,
}))}
excludeSet={[]}
selectedCustomSet={moc_config.event_id.toString()}
@@ -129,7 +131,7 @@ export default function MocBar() {
setSelectedCustomSet={(id) => setMocConfig({
...moc_config,
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>
@@ -149,8 +151,8 @@ export default function MocBar() {
})}
>
<option value={0} disabled={true}>Select a Floor</option>
{mapMOCInfo[moc_config.event_id.toString()]?.map((moc) => (
<option key={moc.Id} value={moc.Id}>{moc.Id % 100}</option>
{eventSelected?.Level?.map((moc) => (
<option key={moc.ID} value={moc.ID}>{getLocaleName(locale, moc.Name)}</option>
))}
</select>
</div>
@@ -205,16 +207,19 @@ export default function MocBar() {
{transI18n("useTurbulenceBuff")}
</span>
</div>
{challengeSelected && challengeSelected?.Desc ? (
<div
className="text-base"
dangerouslySetInnerHTML={{
__html: replaceByParam(
challengeSelected?.Desc || "",
challengeSelected?.Param || []
)
}}
/>
{challengeSelected ? (
challengeSelected.MazeBuff.map((buff, i) => (
<div
key={i}
className="text-base"
dangerouslySetInnerHTML={{
__html: replaceByParam(
getLocaleName(locale, buff?.Desc) || "",
buff?.Param || []
)
}}
/>
))
) : (
<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">
<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">
<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">
{Object.values(wave).map((waveValue, enemyIndex) => (
<div
key={enemyIndex}
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
>
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
className="w-full h-full object-cover"
/>}
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()],
challengeSelected?.EventList1?.[0]?.EliteGroup,
challengeSelected?.EventList1?.[0]?.HardLevelGroup,
challengeSelected?.EventList1?.[0]?.Level,
hardLevelConfig,
eliteConfig
);
return (
<div
key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
>
<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 className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<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">
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt="Enemy Icon"
width={150}
height={150}
className="w-full h-full object-cover"
/>
))}
</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>
))}
@@ -279,49 +321,85 @@ export default function MocBar() {
<div className="rounded-xl p-4 mt-2 border border-warning">
<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">
<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">
{Object.values(wave).map((waveValue, enemyIndex) => (
<div
key={enemyIndex}
className="rounded-xl p-2 border border-white/10 shadow-md hover:border-white/20 hover:shadow-lg transition"
>
<div className="flex items-center space-x-3">
<div className="relative w-20 h-20 rounded-full overflow-hidden shrink-0 border border-white/10 shadow-sm">
{mapMonster?.[waveValue.toString()]?.icon && <Image
unoptimized
crossOrigin="anonymous"
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.icon}`}
alt="Enemy Icon"
width={376}
height={512}
className="w-full h-full object-cover"
/>}
<h3 className="text-lg font-semibold">{transI18n("wave")} {waveIndex + 1}</h3>
<div className="flex flex-wrap gap-2 mt-2">
{Object.values(wave).map((waveValue, enemyIndex) => {
const monsterStats = calcMonsterStats(
mapMonster?.[waveValue.toString()],
challengeSelected?.EventList2?.[0]?.EliteGroup,
challengeSelected?.EventList2?.[0]?.HardLevelGroup,
challengeSelected?.EventList2?.[0]?.Level,
hardLevelConfig,
eliteConfig
);
return (
<div
key={enemyIndex}
className="group relative flex flex-col w-40 bg-base-100 rounded-2xl border border-base-300 shadow-md"
>
<div className="badge badge-warning badge-sm font-bold absolute top-2 right-2 z-10 shadow-sm">
Lv. {challengeSelected?.EventList2[0].Level}
</div>
<div className="flex flex-col">
<div className="text-sm font-semibold">Lv. {challengeSelected?.EventIDList1[0].Level}</div>
<div className="flex items-center space-x-1 mt-1">
{mapMonster?.[waveValue.toString()]?.weak?.map((icon, iconIndex) => (
<div className="relative w-full h-20 bg-base-200 flex items-center justify-center p-4 rounded-t-2xl">
{mapMonster?.[waveValue.toString()]?.Image?.IconPath && (
<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">
<Image
unoptimized
crossOrigin="anonymous"
src={`/icon/${icon.toLowerCase()}.webp`}
alt={icon}
className="h-7 w-7 2xl:h-10 2xl:w-10 object-contain rounded-md border border-white/20 shadow-sm"
width={200}
height={200}
key={iconIndex}
src={`${process.env.CDN_URL}/${mapMonster?.[waveValue.toString()]?.Image?.IconPath}`}
alt="Enemy Icon"
width={150}
height={150}
className="w-full h-full object-cover"
/>
))}
</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>
))}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,14 +2,14 @@
"use client"
import NextImage from "next/image"
import { CharacterDetail, RelicShowcaseType } from "@/types";
import { AvatarDetail, RelicShowcaseType } from "@/types";
export default function RelicShowcase({
relic,
avatarInfo,
}: {
relic: RelicShowcaseType;
avatarInfo: CharacterDetail;
avatarInfo: AvatarDetail;
}) {
return (
<>
@@ -28,7 +28,7 @@ export default function RelicShowcase({
width={78}
height={78}
alt="Relic Icon"
className="h-auto w-19.5 rounded-lg"
className="h-19.5 w-19.5 rounded-lg"
/>
<div
@@ -48,13 +48,13 @@ export default function RelicShowcase({
<div className="relative">
<div className="absolute inset-0 bg-yellow-500/15 rounded-full blur-md -z-10"></div>
<NextImage
src={relic?.mainAffix?.detail?.icon || ""}
src={`${process.env.CDN_URL}/${relic?.mainAffix?.detail?.icon}` || ""}
unoptimized
crossOrigin="anonymous"
width={35}
height={35}
alt="Main Affix Icon"
className="h-auto w-8.75"
className="h-8.75 w-8.75"
/>
</div>
<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">
{subAffix?.detail?.icon ? (
<NextImage
src={subAffix?.detail?.icon || ""}
src={`${process.env.CDN_URL}/${subAffix?.detail?.icon}` || ""}
unoptimized
crossOrigin="anonymous"
width={32}
height={32}
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">

View File

@@ -1,64 +1,47 @@
"use client"
import { useTranslations } from "next-intl";
import useAvatarStore from "@/stores/avatarStore";
import { useMemo } from "react";
import { traceButtonsInfo, traceLink } from "@/constant/traceConstant";
import useUserDataStore from "@/stores/userDataStore";
import useLocaleStore from "@/stores/localeStore";
import Image from "next/image";
import { replaceByParam } from "@/helper";
import { replaceByParam, getLocaleName } from '@/helper';
import { mappingStats } from "@/constant/constant";
import { StatusAddType } from "@/types";
import { toast } from "react-toastify";
import useCurrentDataStore from "@/stores/currentDataStore";
import { StatusAdd } from '@/types/avatarDetail';
export default function SkillsInfo() {
const transI18n = useTranslations("DataPage")
const { theme } = useLocaleStore()
const { avatarSelected, mapAvatarInfo, skillSelected, setSkillSelected } = useAvatarStore()
const { avatarSelected, skillIDSelected, setSkillIDSelected } = useCurrentDataStore()
const { avatars, setAvatar } = useUserDataStore()
const { locale } = useLocaleStore()
const traceButtons = useMemo(() => {
if (!avatarSelected) return
return traceButtonsInfo[avatarSelected.baseType]
return traceButtonsInfo[avatarSelected.BaseType]
}, [avatarSelected])
const avatarInfo = useMemo(() => {
if (!avatarSelected) return
return mapAvatarInfo[avatarSelected.id]
}, [avatarSelected, mapAvatarInfo])
const avatarData = useMemo(() => {
if (!avatarSelected) return
return avatars[avatarSelected.id]
return avatars[avatarSelected?.ID?.toString()]
}, [avatarSelected, avatars])
const avatarSkillTree = useMemo(() => {
if (!avatarSelected || !avatars[avatarSelected.id]) return {}
if (avatars[avatarSelected.id].enhanced) {
return avatarInfo?.Enhanced[avatars[avatarSelected.id].enhanced.toString()].SkillTrees || {}
if (!avatarSelected || !avatars[avatarSelected?.ID?.toString()]) return {}
if (avatars[avatarSelected?.ID?.toString()].enhanced) {
return avatarSelected?.Enhanced?.[avatars[avatarSelected?.ID?.toString()].enhanced.toString()].SkillTrees || {}
}
return avatarInfo?.SkillTrees || {}
}, [avatarSelected, avatarInfo, avatars])
return avatarSelected?.SkillTrees || {}
}, [avatarSelected, avatars])
const skillInfo = useMemo(() => {
if (!avatarSelected || !skillSelected) return
return avatarSkillTree?.[skillSelected || ""]?.["1"]
}, [avatarSelected, avatarSkillTree, skillSelected])
if (!avatarSelected || !skillIDSelected) return
return avatarSkillTree?.[skillIDSelected || ""]?.["1"]
}, [avatarSelected, avatarSkillTree, skillIDSelected])
const getImageSkill = (icon: string | undefined, status: StatusAddType | undefined) => {
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 getTraceBuffDisplay = (status: StatusAdd) => {
const dataDisplay = mappingStats[status.PropertyType]
if (!dataDisplay) return ""
if (dataDisplay.unit === "%") {
@@ -72,11 +55,12 @@ export default function SkillsInfo() {
const dataLevelUpSkill = useMemo(() => {
const skillIds: number[] = skillInfo?.LevelUpSkillID || []
if (!avatarSelected || !avatarInfo || !avatarData) return
let result = Object.values(avatarInfo.Skills || {})?.filter((skill) => skillIds.includes(skill.Id))
if (!avatarSelected || !avatarData) return undefined
let result = Object.values(avatarSelected.Skills || {})?.filter((skill) => skillIds.includes(skill.ID))
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) {
return {
isServant: false,
@@ -84,25 +68,22 @@ export default function SkillsInfo() {
servantData: null,
}
}
const resultServant = Object.entries(avatarInfo.Memosprite?.Skills || {})
?.filter(([skillId]) => skillIds.includes(Number(skillId)))
?.map(([skillId, skillData]) => ({
Id: Number(skillId),
...skillData,
}))
const resultServant = Object.values(avatarSelected?.Memosprite?.Skills || {})
?.filter((skill) => skillIds.includes(skill.ID))
if (resultServant && resultServant.length > 0) {
return {
isServant: true,
data: resultServant,
servantData: avatarInfo.Memosprite,
servantData: avatarSelected.Memosprite,
}
}
return undefined
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarInfo, avatarData])
}, [skillInfo?.LevelUpSkillID, avatarSelected, avatarData])
const handlerMaxAll = () => {
if (!avatarInfo || !avatarData || !avatarSkillTree) {
if (!avatarData || !avatarSkillTree) {
toast.error(transI18n("maxAllFailed"))
return
}
@@ -123,8 +104,8 @@ export default function SkillsInfo() {
const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = status ? 1 : 0
if (!status && traceLink?.[avatarSelected?.baseType || ""]?.[skillSelected || ""]) {
traceLink[avatarSelected?.baseType || ""][skillSelected || ""].forEach((pointId) => {
if (!status && traceLink?.[avatarSelected?.BaseType || ""]?.[skillIDSelected || ""]) {
traceLink[avatarSelected?.BaseType || ""][skillIDSelected || ""].forEach((pointId) => {
if (avatarSkillTree?.[pointId]?.["1"]) {
newData.data.skills[avatarSkillTree?.[pointId]?.["1"].PointID] = 0
}
@@ -143,12 +124,12 @@ export default function SkillsInfo() {
</h2>
<div className="flex flex-col items-center">
<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">
<Image
unoptimized
crossOrigin="anonymous"
src={`/skilltree/${avatarSelected?.baseType?.toUpperCase()}.webp`}
src={`/skilltree/${avatarSelected?.BaseType?.toUpperCase()}.webp`}
alt=""
width={312}
priority={true}
@@ -159,7 +140,7 @@ export default function SkillsInfo() {
className={`w-full h-full object-cover rounded-xl`}
/>
{traceButtons.map((btn, index) => {
if (!avatarInfo?.SkillTrees?.[btn.id]) {
if (!avatarSelected?.SkillTrees?.[btn.id]) {
return null
}
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 === "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" : ""}
${skillSelected === btn.id ? "border-4 border-primary" : ""}
${skillIDSelected === btn.id ? "border-4 border-primary" : ""}
${!avatarData?.data.skills?.[avatarSkillTree?.[btn.id]?.["1"]?.PointID]
? "opacity-50 cursor-not-allowed"
: ""}
`}
onClick={() => {
setSkillSelected(btn.id === skillSelected ? null : btn.id)
setSkillIDSelected(btn.id === skillIDSelected ? null : btn.id)
}}
style={{
left: btn.left,
@@ -193,7 +174,7 @@ export default function SkillsInfo() {
}}
>
<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", "")}
priority={true}
unoptimized
@@ -201,6 +182,8 @@ export default function SkillsInfo() {
width={124}
height={124}
style={{
objectFit: "contain",
padding: "2px",
filter: (btn.size !== "big" && btn.size !== "memory" && btn.size !== "elation") ? "brightness(0%)" : ""
}}
/>
@@ -272,7 +255,7 @@ export default function SkillsInfo() {
</div>
)}
{!traceButtons && avatarInfo && (
{!traceButtons && avatarSelected && (
<div className="flex flex-col relative w-full aspect-square">
</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>
{transI18n("details")}
</h2>
{skillSelected && avatarInfo?.SkillTrees && avatarData && (
{skillIDSelected && avatarSelected?.SkillTrees && avatarData && (
<div>
{skillInfo?.MaxLevel && skillInfo?.MaxLevel > 1 ? (
<div>
@@ -309,14 +292,14 @@ export default function SkillsInfo() {
</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">
<input
type="checkbox"
checked={avatarData?.data.skills?.[skillInfo?.PointID] === 1}
className="toggle toggle-success"
onChange={(e) => {
if (traceButtons?.find((btn) => btn.id === skillSelected)?.size === "special") {
if (traceButtons?.find((btn) => btn.id === skillIDSelected)?.size === "special") {
if (e.target.checked) {
const newData = structuredClone(avatarData)
newData.data.skills[skillInfo?.PointID] = 1
@@ -343,7 +326,7 @@ export default function SkillsInfo() {
(skillInfo?.PointName && skillInfo?.StatusAddList.length > 0))
&& (
<div className="text-xl font-bold flex items-center gap-2 mt-2">
{skillInfo.PointName}
{getLocaleName(locale, skillInfo.PointName)}
{skillInfo.StatusAddList.length > 0 && (
<div>
{skillInfo.StatusAddList.map((status, index) => (
@@ -360,8 +343,8 @@ export default function SkillsInfo() {
<div
dangerouslySetInnerHTML={{
__html: replaceByParam(
skillInfo?.PointDesc || "",
skillInfo?.ParamList || []
getLocaleName(locale, skillInfo?.PointDesc) || "",
skillInfo?.Param || []
)
}}
/>
@@ -377,19 +360,19 @@ export default function SkillsInfo() {
<div key={index}>
<div className="text-xl font-bold text-primary">
{transI18n(dataLevelUpSkill.isServant ? `${skill?.Type ? "severaltalent" : "servantskill"}` : `${skill?.Type ? skill?.Type.toLowerCase() : "talent"}`)}
{` (${transI18n(skill.Tag.toLowerCase())})`}
{transI18n(dataLevelUpSkill.isServant ? `${skill?.AttackType ? "severaltalent" : "servantskill"}` : `${skill?.AttackType ? skill?.AttackType.toLowerCase() : "talent"}`)}
{` (${transI18n(skill?.SkillEffect?.toLowerCase())})`}
</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
dangerouslySetInnerHTML={{
__html: replaceByParam(
skill.Desc || skill.SimpleDesc,
skill.Level[avatarData?.data.skills?.[skillInfo?.PointID]?.toString() || ""]?.ParamList || []
getLocaleName(locale, skill.Desc),
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}> > {
"HPDelta": {
name:"HP",
icon:"/icon/hp.webp",
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "",
baseStat: "HP"
},
"AttackDelta": {
name:"ATK",
icon:"/icon/attack.webp",
icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "",
baseStat: "ATK"
},
"HPAddedRatio": {
name:"HP",
icon:"/icon/hp.webp",
icon:"spriteoutput/ui/avatar/icon/IconMaxHP.png",
unit: "%",
baseStat: "HP"
},
"AttackAddedRatio": {
name:"ATK",
icon:"/icon/attack.webp",
icon:"spriteoutput/ui/avatar/icon/IconAttack.png",
unit: "%",
baseStat: "ATK"
},
"DefenceDelta": {
name:"DEF",
icon:"/icon/defence.webp",
icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "",
baseStat: "DEF"
},
"DefenceAddedRatio": {
name:"DEF",
icon:"/icon/defence.webp",
icon:"spriteoutput/ui/avatar/icon/IconDefence.png",
unit: "%",
baseStat: "DEF"
},
"SpeedAddedRatio": {
name:"SPD",
icon:"/icon/speed.webp",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "%",
baseStat: "SPD"
},
"BaseSpeed": {
name:"SPD",
icon:"/icon/speed.webp",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "",
baseStat: "SPD"
},
"CriticalChanceBase": {
name:"CRIT Rate",
icon:"/icon/crit-rate.webp",
icon:"spriteoutput/ui/avatar/icon/IconCriticalChance.png",
unit: "%",
baseStat: "CRITRate"
},
"CriticalDamageBase": {
name:"CRIT DMG",
icon:"/icon/crit-damage.webp",
icon:"spriteoutput/ui/avatar/icon/IconCriticalDamage.png",
unit: "%",
baseStat: "CRITDmg"
},
"HealRatioBase": {
name:"Outgoing Healing Boost",
icon:"/icon/healing-boost.webp",
icon:"spriteoutput/ui/avatar/icon/IconHealRatio.png",
unit: "%",
baseStat: "HealBoost"
},
"StatusProbabilityBase": {
name:"Effect Hit Rate",
icon:"/icon/effect-hit-rate.webp",
icon:"spriteoutput/ui/avatar/icon/IconStatusProbability.png",
unit: "%",
baseStat: "EffectHitRate"
},
"StatusResistanceBase": {
name:"Effect RES",
icon:"/icon/effect-res.webp",
icon:"spriteoutput/ui/avatar/icon/IconStatusResistance.png",
unit: "%",
baseStat: "EffectRES"
},
"BreakDamageAddedRatioBase": {
name:"Break Effect",
icon:"/icon/break-effect.webp",
icon:"spriteoutput/ui/avatar/icon/IconBreakUp.png",
unit: "%",
baseStat: "BreakEffect"
},
"SpeedDelta": {
name:"SPD",
icon:"/icon/speed.webp",
icon:"spriteoutput/ui/avatar/icon/IconSpeed.png",
unit: "",
baseStat: "SPD"
},
"PhysicalAddedRatio": {
name:"Physical DMG Boost",
icon:"/icon/physical-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconPhysicalAddedRatio.png",
unit: "%",
baseStat: "PhysicalAdd"
},
"FireAddedRatio": {
name:"Fire DMG Boost",
icon:"/icon/fire-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconFireAddedRatio.png",
unit: "%",
baseStat: "FireAdd"
},
"IceAddedRatio": {
name:"Ice DMG Boost",
icon:"/icon/ice-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconIceAddedRatio.png",
unit: "%",
baseStat: "IceAdd"
},
"ThunderAddedRatio": {
name:"Thunder DMG Boost",
icon:"/icon/thunder-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconThunderAddedRatio.png",
unit: "%",
baseStat: "ThunderAdd"
},
"WindAddedRatio": {
name:"Wind DMG Boost",
icon:"/icon/wind-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconWindAddedRatio.png",
unit: "%",
baseStat: "WindAdd"
},
"QuantumAddedRatio": {
name:"Quantum DMG Boost",
icon:"/icon/quantum-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconQuantumAddedRatio.png",
unit: "%",
baseStat: "QuantumAdd"
},
"ImaginaryAddedRatio": {
name:"Imaginary DMG Boost",
icon:"/icon/imaginary-add.webp",
icon:"spriteoutput/ui/avatar/icon/IconImaginaryAddedRatio.png",
unit: "%",
baseStat: "ImaginaryAdd"
},
"ElationDamageAddedRatioBase": {
name:"Elation DMG Boost",
icon:"/icon/IconJoy.webp",
icon:"spriteoutput/ui/avatar/icon/IconJoy.png",
unit: "%",
baseStat: "ElationAdd"
},
"SPRatioBase": {
name:"Energy Regeneration Rate",
icon:"/icon/energy-rate.webp",
icon:"spriteoutput/ui/avatar/icon/IconEnergyRecovery.png",
unit: "%",
baseStat: "EnergyRate"
}
@@ -169,7 +169,14 @@ export const ratioStats = [
"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 }> = {
winter: {

View File

@@ -1,5 +1,5 @@
import { mappingStats, ratioStats } from "@/constant/constant"
import { AffixDetail } from "@/types"
import { EliteData, HardLevelData, MainAffixData, MonsterDetail, SubAffixData} from "@/types"
export function calcPromotion(level: number) {
if (level < 20) {
@@ -37,29 +37,29 @@ export function calcRarity(rarity: string) {
return 1
}
export function calcMainAffixBonus(affix?: AffixDetail, level?: number) {
export function calcMainAffixBonus(affix?: MainAffixData, level?: number) {
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);
}
if (mappingStats?.[affix.property].name === "SPD") {
if (mappingStats?.[affix.Property].name === "SPD") {
return value.toFixed(1);
}
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 (mappingStats?.[affix.property].unit === "%") {
return ((affix.base * rollCount + affix.step * stepCount) * 100).toFixed(1);
if (mappingStats?.[affix.Property].unit === "%") {
return ((affix.BaseValue * rollCount + affix.StepValue * stepCount) * 100).toFixed(1);
}
if (mappingStats?.[affix.property].name === "SPD") {
return (affix.base * rollCount + affix.step * stepCount).toFixed(1);
if (mappingStats?.[affix.Property].name === "SPD") {
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) => {
@@ -72,19 +72,19 @@ export const calcBaseStatRaw = (baseStat?: number, stepStat?: number, level?: nu
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 (ratioStats.includes(affix.property)) {
return (affix.base * rollCount + affix.step * stepCount) * baseStat;
if (ratioStats.includes(affix.Property)) {
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
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
}
@@ -97,4 +97,42 @@ export const calcBonusStatRaw = (affix?: string, baseStat?: number, bonusValue?:
return baseStat * 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 {
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;
}
export function converterToAvatarStore(data: Record<string, CharacterDetail>): { [key: string]: AvatarStore } {
export function converterToAvatarStore(data: Record<string, AvatarDetail>): { [key: string]: AvatarStore } {
return Object.fromEntries(
Object.entries(data).map(([key, value]) => [
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";
@@ -11,7 +12,7 @@ export function converterToFreeSRJson(
ce_config: CEConfigStore,
peak_config: PEAKConfigStore,
): FreeSRJson {
const { SkillTree } = useMazeStore.getState()
const { skillConfig } = useDetailDataStore.getState()
const lightcones: LightconeJson[] = []
const relics: RelicJson[] = []
let battleJson: BattleConfigJson
@@ -84,8 +85,8 @@ export function converterToFreeSRJson(
Object.entries(avatars).forEach(([avatarId, avatar]) => {
const skillsByAnchorType: Record<string, number> = {}
for (const [skillId, level] of Object.entries(avatar?.data?.skills || {})) {
if (SkillTree?.[skillId]) {
skillsByAnchorType[SkillTree[skillId].index_slot] = level > SkillTree[skillId].max_level ? SkillTree[skillId].max_level : level
if (skillConfig?.[skillId]) {
skillsByAnchorType[skillConfig[skillId].IndexSlot] = level > skillConfig[skillId].MaxLevel ? skillConfig[skillId].MaxLevel : level
}
}
avatarsJson[avatarId] = {

View File

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

View File

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

View File

@@ -1,84 +1,41 @@
/* 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 { 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 {
const res = await axios.get<ConfigMaze>(`/data/config_maze.json`);
return res.data as ConfigMaze;
const res = await axios.get<Metadata>(`/api/data/metadata`);
return res.data as Metadata;
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
console.error('Failed to fetch metadata:', error);
return {
Avatar: {},
MOC: {},
AS: {},
PF: {},
BaseType: {},
DamageType: {},
MainAffix: {},
SubAffix: {},
SkillConfig: {},
Stage: {},
Skill: {}
HardLevelConfig: {},
EliteConfig: {}
};
}
}
export async function getMainAffixApi(): Promise<Record<string, Record<string, AffixDetail>>> {
export async function getAvatarListApi(): Promise<Record<string, AvatarDetail>> {
try {
const res = await axios.get<Record<string, Record<string, AffixDetail>>>(`/data/main_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 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];
}
}
const res = await axios.get<Record<string, AvatarDetail>>(`/api/data/avatar`);
return res.data;
} catch (error) {
console.error('Failed to fetch characters:', error);
console.error('Failed to fetch Avatars:', error);
return {};
}
}
export async function fetchLightconesApi(locale: string): Promise<Record<string, LightConeDetail>> {
export async function getLightconeListApi(): Promise<Record<string, LightConeDetail>> {
try {
const res = await axios.get<Record<string, LightConeDetail>>(`/data/lightcones.${locale}.json`);
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];
}
}
const res = await axios.get<Record<string, LightConeDetail>>(`/api/data/lightcone`);
return res.data;
} catch (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 {
const res = await axios.get<Record<string, RelicDetail>>(`/data/relics.${locale}.json`);
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];
}
}
const res = await axios.get<Record<string, RelicSetDetail>>(`/api/data/relic`);
return res.data;
} catch (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 {
const res = await axios.get<Record<string, ASDetail>>(`/data/as.${locale}.json`);
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`);
const res = await axios.get<Record<string, MonsterDetail>>(`/api/data/monster`);
return res.data;
} catch (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 {
const res = await axios.get<CharacterBasic[]>('/data/character.json');
const res = await axios.get<Record<string, ASGroupDetail>>(`/api/data/as`);
return res.data;
} catch (error) {
console.error('Failed to fetch character list:', error);
return [];
console.error('Failed to fetch AS:', error);
return {};
}
}
export async function getLightconeListApi(): Promise<LightConeBasic[]> {
export async function getPFEventListApi(): Promise<Record<string, PFGroupDetail>> {
try {
const res = await axios.get<LightConeBasic[]>('/data/lightcone.json');
return res.data
const res = await axios.get<Record<string, PFGroupDetail>>(`/api/data/pf`);
return res.data;
} catch (error) {
console.error('Failed to fetch lightcone list:', error);
return [];
console.error('Failed to fetch PF:', error);
return {};
}
}
export async function getMOCEventListApi(): Promise<EventBasic[]> {
export async function getMOCEventListApi(): Promise<Record<string, MOCGroupDetail>> {
try {
const res = await axios.get<EventBasic[]>('/data/moc.json');
return res.data
const res = await axios.get<Record<string, MOCGroupDetail>>(`/api/data/moc`);
return res.data;
} catch (error) {
console.error('Failed to fetch moc list:', error);
return [];
console.error('Failed to fetch MOC:', error);
return {};
}
}
export async function getASEventListApi(): Promise<EventBasic[]> {
export async function getPeakEventListApi(): Promise<Record<string, PeakGroupDetail>> {
try {
const res = await axios.get<EventBasic[]>('/data/as.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch as list:', error);
return [];
const res = await axios.get<Record<string, PeakGroupDetail>>(`/api/data/peak`);
return res.data;
} catch (error) {
console.error('Failed to fetch peak:', error);
return {};
}
}
export async function getPFEventListApi(): Promise<EventBasic[]> {
export async function getChangelog(): Promise<ChangelogItemType[]> {
try {
const res = await axios.get<EventBasic[]>('/data/pf.json');
return res.data
} catch (error: unknown) {
console.error('Failed to fetch pf list:', error);
const res = await axios.get<ChangelogItemType[]>(`/api/data/changelog`);
return res.data;
} catch (error) {
console.error('Failed to fetch monster:', error);
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(
username: 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 "./useFetchLightconeData";
export * from "./useFetchRelicData";

View File

@@ -1,72 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"
import { useQuery } from '@tanstack/react-query'
import { fetchASEventApi, getASEventListApi } from '@/lib/api'
import { getASEventListApi } from '@/lib/api'
import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail } from '@/types'
import useDetailDataStore from '@/stores/detailDataStore'
export const useFetchASData = () => {
const { setASEvent, setMapASInfo } = useEventStore()
const { locale } = useLocaleStore()
const { data: dataAS, error: errorAS } = useQuery({
queryKey: ['asData'],
export const useFetchASGroupData = () => {
const { setMapAS } = useDetailDataStore()
const query = useQuery({
queryKey: ['ASGroupData'],
queryFn: getASEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
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(() => {
if (dataAS && !errorAS) {
setASEvent(dataAS)
} else if (errorAS) {
toast.error("Failed to load AS data")
if (query.data) {
setMapAS(query.data)
} else if (query.error) {
toast.error("Failed to load ASGroup data")
}
}, [dataAS, errorAS, setASEvent])
useEffect(() => {
if (dataASInfo && !errorASInfo) {
setMapASInfo(dataASInfo)
} else if (errorASInfo) {
toast.error("Failed to load AS info data")
}
}, [dataASInfo, errorASInfo, setMapASInfo])
}, [query.data, query.error, setMapAS])
return query
}

View File

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

View File

@@ -1,6 +1,6 @@
"use client"
import { useQuery } from '@tanstack/react-query'
import { fetchChangelog } from '@/lib/api'
import { getChangelog } from '@/lib/api'
import { useEffect } from 'react'
import { toast } from 'react-toastify'
import useModelStore from '@/stores/modelStore'
@@ -9,23 +9,24 @@ import useLocaleStore from '@/stores/localeStore'
export const useFetchChangelog = () => {
const { currentVersion, setChangelog, setCurrentVersion } = useLocaleStore()
const { setIsChangelog } = useModelStore()
const { data: dataChangelog, error: errorChangelog } = useQuery({
const query = useQuery({
queryKey: ['changelog'],
queryFn: fetchChangelog,
queryFn: getChangelog,
staleTime: 1000 * 60 * 5,
})
useEffect(() => {
if (dataChangelog && !errorChangelog) {
setChangelog(dataChangelog)
if (dataChangelog?.[0] && dataChangelog[0].version != currentVersion) {
if (query.data && !query.error) {
setChangelog(query.data)
if (query.data?.[0] && query.data[0].version != currentVersion) {
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")
}
// 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"
import { useQuery } from '@tanstack/react-query'
import { fetchLightconesApi, getLightconeListApi } from '@/lib/api'
import { getLightconeListApi } from '@/lib/api';
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 useDetailDataStore from '@/stores/detailDataStore';
export const useFetchLightconeData = () => {
const { setListLightcone, setAllMapLightconeInfo } = useLightconeStore()
const { locale } = useLocaleStore()
const { data: dataLightcone, error: errorLightcone } = useQuery({
const { setMapLightCone } = useDetailDataStore()
const query = useQuery({
queryKey: ['lightconeData'],
queryFn: getLightconeListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
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(() => {
if (dataLightcone && !errorLightcone) {
setListLightcone(dataLightcone)
} else if (errorLightcone) {
if (query.data && !query.error) {
setMapLightCone(query.data)
} else if (query.error) {
toast.error("Failed to load lightcone data")
}
}, [dataLightcone, errorLightcone, setListLightcone])
}, [query.data, query.error, setMapLightCone])
useEffect(() => {
if (dataLightconeInfo && !errorLightconeInfo) {
setAllMapLightconeInfo(dataLightconeInfo)
} else if (errorLightconeInfo) {
toast.error("Failed to load lightcone info data")
}
}, [dataLightconeInfo, errorLightconeInfo, setAllMapLightconeInfo])
return query
}

View File

@@ -1,66 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"
import { useQuery } from '@tanstack/react-query'
import { fetchMOCEventApi, getMOCEventListApi } from '@/lib/api'
import { getMOCEventListApi } from '@/lib/api'
import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail, MocDetail } from '@/types'
export const useFetchMOCData = () => {
const { setMOCEvent, setMapMOCInfo } = useEventStore()
const { locale } = useLocaleStore()
const { data: dataMOC, error: errorMOC } = useQuery({
queryKey: ['mocData'],
export const useFetchMOCGroupData = () => {
const { setMapMoc } = useDetailDataStore()
const query = useQuery({
queryKey: ['MOCGroupData'],
queryFn: getMOCEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
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(() => {
if (dataMOC && !errorMOC) {
setMOCEvent(dataMOC)
} else if (errorMOC) {
toast.error("Failed to load MOC data")
if (query.data && !query.error) {
setMapMoc(query.data)
} else if (query.error) {
toast.error("Failed to load MOCGroup data")
}
}, [dataMOC, errorMOC, setMOCEvent])
}, [query.data, query.error, setMapMoc])
useEffect(() => {
if (dataMOCInfo && !errorMOCInfo) {
setMapMOCInfo(dataMOCInfo as Record<string, MocDetail[]>)
} else if (errorMOCInfo) {
toast.error("Failed to load MOC info data")
}
}, [dataMOCInfo, errorMOCInfo, setMapMOCInfo])
return query
}

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 { getMonsterListApi } from '@/lib/api'
import { useEffect } from 'react'
import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify'
import useMonsterStore from '@/stores/monsterStore'
import { MonsterBasic } from '@/types'
export const useFetchMonsterData = () => {
const { setAllMapMonster, setListMonster } = useMonsterStore()
const { data: dataMonster, error: errorMonster } = useQuery({
queryKey: ['monsterData'],
const { setMapMonster } = useDetailDataStore()
const query = useQuery({
queryKey: ['MonsterData'],
queryFn: getMonsterListApi,
staleTime: 1000 * 60 * 5,
})
useEffect(() => {
if (dataMonster && !errorMonster) {
setListMonster(dataMonster.sort((a, b) => Number(b.id) - Number(a.id)))
const monsterMap = dataMonster.reduce<Record<string, MonsterBasic>>((acc, m) => {
acc[m.id] = m
return acc
}, {})
setAllMapMonster(monsterMap)
} else if (errorMonster) {
toast.error("Failed to load monster data")
if (query.data && !query.error) {
setMapMonster(query.data)
} else if (query.error) {
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"
import { useQuery } from '@tanstack/react-query'
import { fetchPeakEventApi, getPEAKEventListApi } from '@/lib/api'
import { getPeakEventListApi } from '@/lib/api'
import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail, PeakDetail } from '@/types'
export const useFetchPEAKData = () => {
const { setPEAKEvent, setMapPEAKInfo } = useEventStore()
const { locale } = useLocaleStore()
const { data: dataPEAK, error: errorPEAK } = useQuery({
queryKey: ['peakData'],
queryFn: getPEAKEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
export const useFetchPeakGroupData = () => {
const { setMapPeak } = useDetailDataStore()
const query = useQuery({
queryKey: ['PeakGroupData'],
queryFn: getPeakEventListApi,
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(() => {
if (dataPEAK && !errorPEAK) {
setPEAKEvent(dataPEAK)
} else if (errorPEAK) {
toast.error("Failed to load PEAK data")
if (query.data && !query.error) {
setMapPeak(query.data)
} else if (query.error) {
toast.error("Failed to load PeakGroup data")
}
}, [dataPEAK, errorPEAK, setPEAKEvent])
useEffect(() => {
if (dataPEAKInfo && !errorPEAKInfo) {
setMapPEAKInfo(dataPEAKInfo as Record<string, PeakDetail>)
} else if (errorPEAKInfo) {
toast.error("Failed to load PEAK info data")
}
}, [dataPEAKInfo, errorPEAKInfo, setMapPEAKInfo])
}, [query.data, query.error, setMapPeak])
return query
}

View File

@@ -1,64 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
"use client"
import { useQuery } from '@tanstack/react-query'
import { fetchPFEventApi, getPFEventListApi } from '@/lib/api'
import { getPFEventListApi } from '@/lib/api'
import { useEffect } from 'react'
import { listCurrentLanguageApi } from '@/constant/constant'
import useLocaleStore from '@/stores/localeStore'
import useDetailDataStore from '@/stores/detailDataStore'
import { toast } from 'react-toastify'
import useEventStore from '@/stores/eventStore'
import { EventStageDetail } from '@/types'
export const useFetchPFData = () => {
const { setPFEvent, setMapPFInfo } = useEventStore()
const { locale } = useLocaleStore()
const { data: dataPF, error: errorPF } = useQuery({
queryKey: ['pfData'],
export const useFetchPFGroupData = () => {
const { setMapPF } = useDetailDataStore()
const query = useQuery({
queryKey: ['PFGroupData'],
queryFn: getPFEventListApi,
select: (data) => data.sort((a, b) => Number(b.id) - Number(a.id)),
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(() => {
if (dataPF && !errorPF) {
setPFEvent(dataPF)
} else if (errorPF) {
toast.error("Failed to load PF data")
if (query.data && !query.error) {
setMapPF(query.data)
} else if (query.error) {
toast.error("Failed to load PFGroup data")
}
}, [dataPF, errorPF, setPFEvent])
useEffect(() => {
if (dataPFInfo && !errorPFInfo) {
setMapPFInfo(dataPFInfo)
} else if (errorPFInfo) {
toast.error("Failed to load PF info data")
}
}, [dataPFInfo, errorPFInfo, setMapPFInfo])
}, [query.data, query.error, setMapPF])
return query
}

View File

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