feat: enhance asset management with weapon and relic stores, add fight property enums
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// source: buzzy
|
||||
|
||||
export const star = (key: number) => (
|
||||
<svg
|
||||
key={key}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
style={{
|
||||
fill: "rgba(255,204,50)",
|
||||
filter: "drop-shadow(#e3721b 0px 0px 1px)",
|
||||
height: 20,
|
||||
width: 20,
|
||||
}}
|
||||
>
|
||||
<title>Star</title>
|
||||
<path d="M381.2 150.3l143.7 21.2c11.9 1.7 21.9 10.1 25.7 21.6 3.8 11.6.7 24.2-7.9 32.8L438.5 328.1l24.6 146.6c2 12-2.9 24.2-12.9 31.3-9.9 7.1-23 8-33.7 2.3l-128.4-68.5-128.3 68.5c-10.8 5.7-23.9 4.8-33.8-2.3-9.9-7.1-14.9-19.3-12.8-31.3l24.6-146.6L33.58 225.9c-8.61-8.6-11.67-21.2-7.89-32.8 3.77-11.5 13.74-19.9 25.73-21.6L195 150.3l64.4-132.33C264.7 6.954 275.9-.04 288.1-.04c12.3 0 23.5 6.994 28.8 18.01l64.3 132.33z"></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,82 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export function WeaponGlazeSvg({
|
||||
tw,
|
||||
style,
|
||||
}: {
|
||||
tw: string;
|
||||
style: CSSProperties;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 7 1"
|
||||
tw={tw}
|
||||
style={style}
|
||||
>
|
||||
<title>Weapon Glaze</title>
|
||||
<defs>
|
||||
<filter
|
||||
id="svg-Sheen__a"
|
||||
width="1.028"
|
||||
height="2.42"
|
||||
x="-.014"
|
||||
y="-.71"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feGaussianBlur stdDeviation=".037"></feGaussianBlur>
|
||||
</filter>
|
||||
<filter
|
||||
id="svg-Sheen__b"
|
||||
width="1"
|
||||
height="1.013"
|
||||
x="0"
|
||||
y="-.007"
|
||||
style={{ colorInterpolationFilters: "sRGB" }}
|
||||
>
|
||||
<feGaussianBlur></feGaussianBlur>
|
||||
</filter>
|
||||
<filter
|
||||
id="svg-Sheen__c"
|
||||
width="1.004"
|
||||
height="1.613"
|
||||
x="-.002"
|
||||
y="-.306"
|
||||
style={{ colorInterpolationFilters: "sRGB" }}
|
||||
>
|
||||
<feGaussianBlur stdDeviation=".005"></feGaussianBlur>
|
||||
</filter>
|
||||
</defs>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m77.486 180.56 3.227-.063 3.227.063-3.227.063z"
|
||||
filter="url(#svg-Sheen__a)"
|
||||
style={{ mixBlendMode: "normal", filter: "url(#svg-Sheen__a)" }}
|
||||
transform="matrix(.8616 0 0 3.6457 -66.042 -657.768)"
|
||||
></path>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m77.457 180.56 3.256-.059 3.257.059-3.257.059z"
|
||||
opacity=".7"
|
||||
style={{
|
||||
opacity: 0.7,
|
||||
strokeWidth: "1.09436",
|
||||
filter: "url(#svg-Sheen__b)",
|
||||
}}
|
||||
transform="matrix(.8168 0 0 1.54812 -62.427 -279.03)"
|
||||
></path>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="m77.457 180.56 3.256-.021 3.257.021-3.257.021z"
|
||||
opacity=".7"
|
||||
style={{
|
||||
opacity: 1,
|
||||
mixBlendMode: "normal",
|
||||
strokeWidth: "0.661912",
|
||||
filter: 'url("#svg-Sheen__c")',
|
||||
}}
|
||||
transform="matrix(.81587 0 0 1 -62.351 -180.06)"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
+55
-6
@@ -2,7 +2,7 @@ import { mkdir, readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { file, hash, write } from "bun";
|
||||
import { TTLCache } from "enka.ts";
|
||||
import { AvatarStore } from "@/type/enka";
|
||||
import { AvatarStore, EnkaLocs, RelicStore, WeaponStore } from "@/type/enka";
|
||||
|
||||
export const cache = new TTLCache();
|
||||
|
||||
@@ -10,17 +10,21 @@ export class AssetManager {
|
||||
private cache = new Map<string, ArrayBuffer>();
|
||||
|
||||
async getMissingAssets() {
|
||||
const store = await getEnkaAvatarStore();
|
||||
const avatars = await getEnkaAvatarStore();
|
||||
const weapons = await getEnkaWeaponStore();
|
||||
const relics = await getEnkaRelicStore();
|
||||
await mkdir("store").catch(() => {});
|
||||
const existing = await readdir("store");
|
||||
const missing = new Set<string>();
|
||||
Object.values(store)
|
||||
.flatMap((a) => [
|
||||
[
|
||||
...Object.values(avatars).flatMap((a) => [
|
||||
...a.Consts,
|
||||
...Object.values(a.Skills),
|
||||
a.SideIconName.replace("AvatarIcon_Side", "Gacha_AvatarImg"),
|
||||
])
|
||||
.map((n) => existing.includes(hash(n).toString(36)) || missing.add(n));
|
||||
]),
|
||||
...Object.values(weapons).flatMap((w) => [w.Icon, w.AwakenIcon]),
|
||||
...Object.values(relics.Items).map((r) => r.Icon),
|
||||
].map((n) => existing.includes(hash(n).toString(36)) || missing.add(n));
|
||||
|
||||
console.log(
|
||||
`${missing.size} assets missing, ${existing.length} assets in store`,
|
||||
@@ -89,6 +93,15 @@ export class AssetManager {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEnkaStores() {
|
||||
return Promise.all([
|
||||
getEnkaAvatarStore(),
|
||||
getEnkaWeaponStore(),
|
||||
getEnkaRelicStore(),
|
||||
getEnkaLocs(),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function getEnkaAvatarStore(): Promise<AvatarStore> {
|
||||
const cached = cache.get("enkaAvatarStore");
|
||||
if (cached) return cached as AvatarStore;
|
||||
@@ -100,3 +113,39 @@ export async function getEnkaAvatarStore(): Promise<AvatarStore> {
|
||||
cache.set("enkaAvatarStore", data, 86400); // 24 hours
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getEnkaWeaponStore(): Promise<WeaponStore> {
|
||||
const cached = cache.get("enkaWeaponStore");
|
||||
if (cached) return cached as WeaponStore;
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/EnkaNetwork/API-docs/raw/refs/heads/master/store/gi/weapons.json",
|
||||
);
|
||||
const data = await response.json();
|
||||
cache.set("enkaWeaponStore", data, 86400); // 24 hours
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getEnkaRelicStore(): Promise<RelicStore> {
|
||||
const cached = cache.get("enkaRelicStore");
|
||||
if (cached) return cached as RelicStore;
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/EnkaNetwork/API-docs/raw/refs/heads/master/store/gi/relics.json",
|
||||
);
|
||||
const data = await response.json();
|
||||
cache.set("enkaRelicStore", data, 86400); // 24 hours
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getEnkaLocs(): Promise<EnkaLocs> {
|
||||
const cached = cache.get("enkaLocs");
|
||||
if (cached) return cached as EnkaLocs;
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/EnkaNetwork/API-docs/raw/refs/heads/master/store/gi/locs.json",
|
||||
);
|
||||
const data = await response.json();
|
||||
cache.set("enkaLocs", data, 86400); // 24 hours
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -65,3 +65,44 @@ export type WeaponType =
|
||||
| "WEAPON_BOW";
|
||||
|
||||
export type QualityType = "QUALITY_ORANGE" | "QUALITY_PURPLE";
|
||||
|
||||
export interface WeaponConfig {
|
||||
Rarity: number;
|
||||
WeaponType: number;
|
||||
Icon: string;
|
||||
AwakenIcon: string;
|
||||
NameTextMapHash: number;
|
||||
|
||||
BaseProps: Record<string, number>;
|
||||
PropGrowCurves: Record<string, number>;
|
||||
|
||||
BasePromote: number[];
|
||||
RefineProps: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type WeaponStore = Record<string, WeaponConfig>;
|
||||
|
||||
export interface RelicConfig {
|
||||
Rarity: number;
|
||||
EquipType: number;
|
||||
Icon: string;
|
||||
SetId: number;
|
||||
}
|
||||
|
||||
export type RelicConfigMap = Record<string, RelicConfig>;
|
||||
|
||||
export interface RelicSetBonus {
|
||||
Name: number; // TextMapHash
|
||||
AddProps: Record<number, number>;
|
||||
}
|
||||
|
||||
export type RelicSetMap = Record<number, RelicSetBonus>;
|
||||
|
||||
export interface RelicStore {
|
||||
Items: RelicConfigMap;
|
||||
Sets: RelicSetMap;
|
||||
}
|
||||
|
||||
export interface EnkaLocs {
|
||||
en: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
enum FightPropMapEnum {
|
||||
None = 0,
|
||||
|
||||
// Base Stats
|
||||
BaseHP = 1, // FIGHT_PROP_BASE_HP
|
||||
HP_Flat = 2, // FIGHT_PROP_HP (flat HP bonus)
|
||||
HPPercentage = 3, // FIGHT_PROP_HP_PERCENT
|
||||
BaseAttack = 4, // FIGHT_PROP_BASE_ATTACK
|
||||
Attack_Flat = 5, // FIGHT_PROP_ATTACK (flat ATK bonus)
|
||||
AttackPercentage = 6, // FIGHT_PROP_ATTACK_PERCENT
|
||||
BaseDefense = 7, // FIGHT_PROP_BASE_DEFENSE
|
||||
Defense_Flat = 8, // FIGHT_PROP_DEFENSE (flat DEF bonus)
|
||||
DefensePercentage = 9, // FIGHT_PROP_DEFENSE_PERCENT
|
||||
BaseSpeed = 10, // FIGHT_PROP_BASE_SPEED
|
||||
SpeedPercentage = 11, // FIGHT_PROP_SPEED_PERCENT
|
||||
HPMPPercentage = 12, // FIGHT_PROP_HP_MP_PERCENT
|
||||
AttackMPPercentage = 13, // FIGHT_PROP_ATTACK_MP_PERCENT
|
||||
|
||||
// Critical Stats
|
||||
CriticalRate = 20, // FIGHT_PROP_CRITICAL
|
||||
AntiCritical = 21, // FIGHT_PROP_ANTI_CRITICAL
|
||||
CriticalDamage = 22, // FIGHT_PROP_CRITICAL_HURT
|
||||
|
||||
// Energy & Mastery
|
||||
EnergyRecharge = 23, // FIGHT_PROP_CHARGE_EFFICIENCY
|
||||
AddHurt = 24, // FIGHT_PROP_ADD_HURT (all damage bonus)
|
||||
SubHurt = 25, // FIGHT_PROP_SUB_HURT (all damage reduction)
|
||||
HealingBonus = 26, // FIGHT_PROP_HEAL_ADD
|
||||
IncomingHealingBonus = 27, // FIGHT_PROP_HEALED_ADD
|
||||
ElementalMastery = 28, // FIGHT_PROP_ELEMENT_MASTERY
|
||||
|
||||
// Physical Stats
|
||||
PhysicalResistance = 29, // FIGHT_PROP_PHYSICAL_SUB_HURT
|
||||
PhysicalDamageBonus = 30, // FIGHT_PROP_PHYSICAL_ADD_HURT
|
||||
|
||||
// Defense Ignore
|
||||
DefenseIgnoreRatio = 31, // FIGHT_PROP_DEFENCE_IGNORE_RATIO
|
||||
DefenseIgnoreDelta = 32, // FIGHT_PROP_DEFENCE_IGNORE_DELTA
|
||||
|
||||
// Elemental Damage Bonuses
|
||||
PyroDamageBonus = 40, // FIGHT_PROP_FIRE_ADD_HURT
|
||||
ElectroDamageBonus = 41, // FIGHT_PROP_ELEC_ADD_HURT
|
||||
HydroDamageBonus = 42, // FIGHT_PROP_WATER_ADD_HURT
|
||||
DendroDamageBonus = 43, // FIGHT_PROP_GRASS_ADD_HURT
|
||||
AnemoDamageBonus = 44, // FIGHT_PROP_WIND_ADD_HURT
|
||||
GeoDamageBonus = 45, // FIGHT_PROP_ROCK_ADD_HURT
|
||||
CryoDamageBonus = 46, // FIGHT_PROP_ICE_ADD_HURT
|
||||
HeadAddHurt = 47, // FIGHT_PROP_HIT_HEAD_ADD_HURT
|
||||
|
||||
// Elemental Resistances
|
||||
PyroResistance = 50, // FIGHT_PROP_FIRE_SUB_HURT
|
||||
ElectroResistance = 51, // FIGHT_PROP_ELEC_SUB_HURT
|
||||
HydroResistance = 52, // FIGHT_PROP_WATER_SUB_HURT
|
||||
DendroResistance = 53, // FIGHT_PROP_GRASS_SUB_HURT
|
||||
AnemoResistance = 54, // FIGHT_PROP_WIND_SUB_HURT
|
||||
GeoResistance = 55, // FIGHT_PROP_ROCK_SUB_HURT
|
||||
CryoResistance = 56, // FIGHT_PROP_ICE_SUB_HURT
|
||||
|
||||
// Effect Stats
|
||||
EffectHit = 60, // FIGHT_PROP_EFFECT_HIT
|
||||
EffectResist = 61, // FIGHT_PROP_EFFECT_RESIST
|
||||
FreezeResist = 62, // FIGHT_PROP_FREEZE_RESIST
|
||||
DizzyResist = 64, // FIGHT_PROP_DIZZY_RESIST
|
||||
FreezeShorten = 65, // FIGHT_PROP_FREEZE_SHORTEN
|
||||
DizzyShorten = 67, // FIGHT_PROP_DIZZY_SHORTEN
|
||||
|
||||
// Max Energy
|
||||
MaxPyroEnergy = 70, // FIGHT_PROP_MAX_FIRE_ENERGY
|
||||
MaxElectroEnergy = 71, // FIGHT_PROP_MAX_ELEC_ENERGY
|
||||
MaxHydroEnergy = 72, // FIGHT_PROP_MAX_WATER_ENERGY
|
||||
MaxDendroEnergy = 73, // FIGHT_PROP_MAX_GRASS_ENERGY
|
||||
MaxAnemoEnergy = 74, // FIGHT_PROP_MAX_WIND_ENERGY
|
||||
MaxCryoEnergy = 75, // FIGHT_PROP_MAX_ICE_ENERGY
|
||||
MaxGeoEnergy = 76, // FIGHT_PROP_MAX_ROCK_ENERGY
|
||||
|
||||
// Utility
|
||||
CooldownReduction = 80, // FIGHT_PROP_SKILL_CD_MINUS_RATIO
|
||||
ShieldStrength = 81, // FIGHT_PROP_SHIELD_COST_MINUS_RATIO
|
||||
|
||||
// Current Energy
|
||||
CurrentPyroEnergy = 1000, // FIGHT_PROP_CUR_FIRE_ENERGY
|
||||
CurrentElectroEnergy = 1001, // FIGHT_PROP_CUR_ELEC_ENERGY
|
||||
CurrentHydroEnergy = 1002, // FIGHT_PROP_CUR_WATER_ENERGY
|
||||
CurrentDendroEnergy = 1003, // FIGHT_PROP_CUR_GRASS_ENERGY
|
||||
CurrentAnemoEnergy = 1004, // FIGHT_PROP_CUR_WIND_ENERGY
|
||||
CurrentCryoEnergy = 1005, // FIGHT_PROP_CUR_ICE_ENERGY
|
||||
CurrentGeoEnergy = 1006, // FIGHT_PROP_CUR_ROCK_ENERGY
|
||||
CurrentHP = 1010, // FIGHT_PROP_CUR_HP
|
||||
|
||||
// Final Stats
|
||||
HP = 2000, // FIGHT_PROP_MAX_HP
|
||||
Attack = 2001, // FIGHT_PROP_CUR_ATTACK
|
||||
Defense = 2002, // FIGHT_PROP_CUR_DEFENSE
|
||||
Speed = 2003, // FIGHT_PROP_CUR_SPEED
|
||||
CurrentHPDebts = 2004, // FIGHT_PROP_CUR_HP_DEBTS
|
||||
CurrentHPPaidDebts = 2005, // FIGHT_PROP_CUR_HP_PAID_DEBTS
|
||||
|
||||
// Non-Extra Stats (base stats without bonuses)
|
||||
NonExtraAttack = 3000, // FIGHT_PROP_NONEXTRA_ATTACK
|
||||
NonExtraDefense = 3001, // FIGHT_PROP_NONEXTRA_DEFENSE
|
||||
NonExtraCritical = 3002, // FIGHT_PROP_NONEXTRA_CRITICAL
|
||||
NonExtraAntiCritical = 3003, // FIGHT_PROP_NONEXTRA_ANTI_CRITICAL
|
||||
NonExtraCriticalHurt = 3004, // FIGHT_PROP_NONEXTRA_CRITICAL_HURT
|
||||
NonExtraChargeEfficiency = 3005, // FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY
|
||||
NonExtraElementMastery = 3006, // FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY
|
||||
NonExtraPhysicalSubHurt = 3007, // FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT
|
||||
NonExtraPyroDamageBonus = 3008, // FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT
|
||||
NonExtraElectroDamageBonus = 3009, // FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT
|
||||
NonExtraHydroDamageBonus = 3010, // FIGHT_PROP_NONEXTRA_WATER_ADD_HURT
|
||||
NonExtraDendroDamageBonus = 3011, // FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT
|
||||
NonExtraAnemoDamageBonus = 3012, // FIGHT_PROP_NONEXTRA_WIND_ADD_HURT
|
||||
NonExtraGeoDamageBonus = 3013, // FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT
|
||||
NonExtraCryoDamageBonus = 3014, // FIGHT_PROP_NONEXTRA_ICE_ADD_HURT
|
||||
NonExtraPyroResistance = 3015, // FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT
|
||||
NonExtraElectroResistance = 3016, // FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT
|
||||
NonExtraHydroResistance = 3017, // FIGHT_PROP_NONEXTRA_WATER_SUB_HURT
|
||||
NonExtraDendroResistance = 3018, // FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT
|
||||
NonExtraAnemoResistance = 3019, // FIGHT_PROP_NONEXTRA_WIND_SUB_HURT
|
||||
NonExtraGeoResistance = 3020, // FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT
|
||||
NonExtraCryoResistance = 3021, // FIGHT_PROP_NONEXTRA_ICE_SUB_HURT
|
||||
NonExtraCooldownReduction = 3022, // FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO
|
||||
NonExtraShieldStrength = 3023, // FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO
|
||||
NonExtraPhysicalDamageBonus = 3024, // FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT
|
||||
|
||||
// Elemental Reaction Stats
|
||||
ElementalReactionCritRate = 3025, // FIGHT_PROP_ELEM_REACT_CRITICAL
|
||||
ElementalReactionCritDamage = 3026, // FIGHT_PROP_ELEM_REACT_CRITICAL_HURT
|
||||
OverloadedCritRate = 3027, // FIGHT_PROP_ELEM_REACT_EXPLODE_CRITICAL
|
||||
OverloadedCritDamage = 3028, // FIGHT_PROP_ELEM_REACT_EXPLODE_CRITICAL_HURT
|
||||
SwirlCritRate = 3029, // FIGHT_PROP_ELEM_REACT_SWIRL_CRITICAL
|
||||
SwirlCritDamage = 3030, // FIGHT_PROP_ELEM_REACT_SWIRL_CRITICAL_HURT
|
||||
ElectroChargedCritRate = 3031, // FIGHT_PROP_ELEM_REACT_ELECTRIC_CRITICAL
|
||||
ElectroChargedCritDamage = 3032, // FIGHT_PROP_ELEM_REACT_ELECTRIC_CRITICAL_HURT
|
||||
SuperconductCritRate = 3033, // FIGHT_PROP_ELEM_REACT_SCONDUCT_CRITICAL
|
||||
SuperconductCritDamage = 3034, // FIGHT_PROP_ELEM_REACT_SCONDUCT_CRITICAL_HURT
|
||||
BurnCritRate = 3035, // FIGHT_PROP_ELEM_REACT_BURN_CRITICAL
|
||||
BurnCritDamage = 3036, // FIGHT_PROP_ELEM_REACT_BURN_CRITICAL_HURT
|
||||
ShatteredCritRate = 3037, // FIGHT_PROP_ELEM_REACT_FROZENBROKEN_CRITICAL
|
||||
ShatteredCritDamage = 3038, // FIGHT_PROP_ELEM_REACT_FROZENBROKEN_CRITICAL_HURT
|
||||
BloomCritRate = 3039, // FIGHT_PROP_ELEM_REACT_OVERGROW_CRITICAL
|
||||
BloomCritDamage = 3040, // FIGHT_PROP_ELEM_REACT_OVERGROW_CRITICAL_HURT
|
||||
BurgeonCritRate = 3041, // FIGHT_PROP_ELEM_REACT_OVERGROW_FIRE_CRITICAL
|
||||
BurgeonCritDamage = 3042, // FIGHT_PROP_ELEM_REACT_OVERGROW_FIRE_CRITICAL_HURT
|
||||
HyperbloomCritRate = 3043, // FIGHT_PROP_ELEM_REACT_OVERGROW_ELECTRIC_CRITICAL
|
||||
HyperbloomCritDamage = 3044, // FIGHT_PROP_ELEM_REACT_OVERGROW_ELECTRIC_CRITICAL_HURT
|
||||
BaseElementalReactionCritRate = 3045, // FIGHT_PROP_BASE_ELEM_REACT_CRITICAL
|
||||
BaseElementalReactionCritDamage = 3046, // FIGHT_PROP_BASE_ELEM_REACT_CRITICAL_HURT
|
||||
}
|
||||
+103
-14
@@ -1,3 +1,4 @@
|
||||
import { feature } from "bun:bundle";
|
||||
import { ImageResponse } from "@takumi-rs/image-response";
|
||||
import { file, hash } from "bun";
|
||||
import { NotFoundError, status, t } from "elysia";
|
||||
@@ -16,8 +17,11 @@ import { ConstsSvg } from "@/assets/consts";
|
||||
import { featherImage } from "@/assets/feather";
|
||||
import shadeSvg from "@/assets/shade.svg" with { type: "file" };
|
||||
import fontFile from "@/assets/shinshin.woff2" with { type: "file" };
|
||||
import { star } from "@/assets/stars";
|
||||
import { TalentSvg } from "@/assets/talent";
|
||||
import { AssetManager, getEnkaAvatarStore } from "@/lib/assets";
|
||||
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
||||
import { AssetManager, getEnkaStores } from "@/lib/assets";
|
||||
import { mockData } from "@/lib/mock";
|
||||
|
||||
const assets = new AssetManager();
|
||||
const cache = new TTLCache();
|
||||
@@ -38,16 +42,19 @@ export function card(app: App) {
|
||||
async ({ params: { uid, char: charId }, store: { start } }) => {
|
||||
try {
|
||||
// const data = mockData;
|
||||
const [avatarStore, data] = await Promise.all([
|
||||
getEnkaAvatarStore().finally(() =>
|
||||
console.log(
|
||||
`\n${Date.now() - start}ms - Avatar store loaded`,
|
||||
const [[avatarStore, weaponStore, relicStore, locs], data] =
|
||||
await Promise.all([
|
||||
getEnkaStores().finally(() =>
|
||||
console.log(`\n${Date.now() - start}ms - Stores loaded`),
|
||||
),
|
||||
),
|
||||
fetchEnka(uid, false).finally(() =>
|
||||
console.log(`${Date.now() - start}ms - Enka data loaded`),
|
||||
),
|
||||
]);
|
||||
feature("API")
|
||||
? fetchEnka(uid, false).finally(() =>
|
||||
console.log(
|
||||
`${Date.now() - start}ms - Enka data loaded`,
|
||||
),
|
||||
)
|
||||
: mockData,
|
||||
]);
|
||||
if (typeof data.avatarInfoList === "undefined")
|
||||
return status(400, "The showcase for this UID is private");
|
||||
const char = data.avatarInfoList.find(
|
||||
@@ -62,11 +69,33 @@ export function card(app: App) {
|
||||
"Unsupported character, avatar store doesn't have this character",
|
||||
);
|
||||
|
||||
if (!char.equipList)
|
||||
return status(501, "INTERNAL: No equipList provided");
|
||||
const weapon = {
|
||||
data: char.equipList.find((e) => e.weapon)!.weapon!,
|
||||
store:
|
||||
weaponStore[
|
||||
char.equipList.find((e) => e.weapon)!.itemId ?? "Unknown"
|
||||
],
|
||||
};
|
||||
|
||||
const cacheKey = hash(JSON.stringify(data)).toString(36);
|
||||
const cached = cache.get<ImageResponse>(cacheKey);
|
||||
if (cached) return cached.clone();
|
||||
if (feature("API")) {
|
||||
const cached = cache.get<ImageResponse>(cacheKey);
|
||||
if (cached) return cached.clone();
|
||||
}
|
||||
|
||||
const resources = await Promise.all([
|
||||
assets
|
||||
.load(
|
||||
weapon.data.promoteLevel > 2
|
||||
? weapon.store.AwakenIcon
|
||||
: weapon.store.Icon,
|
||||
)
|
||||
.then((data) => ({
|
||||
src: "/weapon",
|
||||
data,
|
||||
})),
|
||||
...[...avatar.Consts, ...Object.values(avatar.Skills)].map(
|
||||
async (path) => ({
|
||||
src: path,
|
||||
@@ -97,7 +126,10 @@ export function card(app: App) {
|
||||
src: "/bg",
|
||||
data,
|
||||
})),
|
||||
]);
|
||||
] satisfies Promise<{
|
||||
src: string;
|
||||
data: ArrayBuffer | Buffer;
|
||||
}>[]);
|
||||
console.log(
|
||||
`${Date.now() - start}ms - Image resources loaded\n${resources.map((r) => `- ${r.src} ${(r.data.byteLength / 1000).toFixed(2)}Kb`).join("\n")}`,
|
||||
);
|
||||
@@ -232,7 +264,46 @@ export function card(app: App) {
|
||||
</div>
|
||||
</div>
|
||||
{/* middle section */}
|
||||
<div tw="absolute top-0 bottom-0 right-[23.1em] w-[21.5em] p-[1.4em_.7em_.7em]"></div>
|
||||
<div tw="absolute top-0 bottom-0 right-[23.1em] w-[21.5em] p-[1.4em_.7em_.7em] flex flex-col">
|
||||
{/* weapon */}
|
||||
<div tw="flex flex-start" className="Weapon">
|
||||
{/* display */}
|
||||
<div tw="w-[27%] leading-none shrink-0 min-h-[5.5em] relative">
|
||||
<img src="/weapon" alt="Weapon" tw="w-full" />
|
||||
<WeaponGlazeSvg
|
||||
tw="absolute top-[90%] -left-[15%] w-[130%]"
|
||||
style={{
|
||||
color: {
|
||||
1: "#868a82",
|
||||
2: "#70ca7a",
|
||||
3: "#5cabff",
|
||||
4: "#cc7bff",
|
||||
5: "#dd8637",
|
||||
}[weapon.store.Rarity],
|
||||
}}
|
||||
/>
|
||||
<div tw="absolute w-full top-[93%] flex justify-center">
|
||||
{Array.from({ length: weapon.store.Rarity }, (_, i) =>
|
||||
star(i),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* caption */}
|
||||
<div tw="px-[5%] grow text-white self-center">
|
||||
<div
|
||||
tw="text-[108%] leading-[1.3]"
|
||||
style={{
|
||||
textShadow:
|
||||
"0 .08em .1em rgba(0,0,0,.4),0 .1em .3em rgba(0,0,0,.4)",
|
||||
}}
|
||||
>
|
||||
<span>{locs.en[weapon.store.NameTextMapHash]}</span>
|
||||
</div>
|
||||
{/* stats */}
|
||||
<div tw="pt-[3%] pb-[2%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* right section */}
|
||||
<div tw="absolute top-0 bottom-0 right-0 w-[23.1em] p-[.7em]"></div>
|
||||
</div>,
|
||||
@@ -261,6 +332,24 @@ export function card(app: App) {
|
||||
.Talents.Fire { color: #541900!important }
|
||||
.Grass { color: #a5ffab; }
|
||||
.Talents.Grass { color: #134517!important }
|
||||
|
||||
.Substat {
|
||||
display: inline-block;
|
||||
background: rgba(255,255,255,.15);
|
||||
border-radius: .2em;
|
||||
padding: 2% 3%;
|
||||
box-sizing: content-box;
|
||||
margin-right: .5em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.Weapon .Substat:before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: .2em
|
||||
}
|
||||
`,
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user