448 lines
20 KiB
Vue
448 lines
20 KiB
Vue
<template>
|
|
<div class="h-full flex flex-col p-4 gap-4 bg-[#1b1026] overflow-y-auto custom-scrollbar">
|
|
<!-- Pet Avatar -->
|
|
<PixelFrame class="flex-shrink-0" title="寵物資訊">
|
|
<!-- Helper Buttons Overlay -->
|
|
<div class="absolute top-1 right-1 z-30 flex gap-1">
|
|
<button
|
|
@click="$emit('openAchievements')"
|
|
class="p-1 bg-[#2b193f] border border-[#f6b26b] hover:bg-[#3d2459] active:translate-y-0.5 group"
|
|
title="成就"
|
|
>
|
|
<Trophy :size="14" class="text-[#f6b26b] group-hover:text-white" />
|
|
</button>
|
|
<button
|
|
@click="$emit('deletePet')"
|
|
class="p-1 bg-[#2b193f] border border-[#d95763] hover:bg-[#3d2459] active:translate-y-0.5 group"
|
|
title="刪除寵物"
|
|
>
|
|
<Trash2 :size="14" class="text-[#d95763] group-hover:text-white" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex flex-col items-center p-1 relative">
|
|
<div class="w-20 h-20 bg-[#1b1026] border-4 border-[#4a3b5e] mb-2 relative overflow-visible group shadow-inner flex items-center justify-center">
|
|
<!-- Background for portrait -->
|
|
<div class="absolute inset-0 bg-[#2b193f] opacity-50 overflow-hidden" />
|
|
|
|
<!-- The Animated Pixel Avatar -->
|
|
<div class="scale-110 transform translate-y-1 relative z-10">
|
|
<PixelAvatar
|
|
skinColor="#ffdbac"
|
|
hairColor="#e0d8f0"
|
|
outfitColor="#9fd75b"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Scanline on portrait -->
|
|
<div class="absolute inset-0 bg-[linear-gradient(rgba(0,0,0,0)_50%,rgba(0,0,0,0.2)_50%)] bg-[length:100%_4px] pointer-events-none z-20 overflow-hidden" />
|
|
|
|
<!-- Mood Bubble (Shows on Hover) -->
|
|
<div class="absolute -top-6 -right-6 z-30 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none">
|
|
<div class="relative bg-[#e0d8f0] text-[#1b1026] p-2 border-2 border-[#1b1026] shadow-[4px_4px_0_rgba(0,0,0,0.2)]">
|
|
<!-- Pixel Tail -->
|
|
<div class="absolute bottom-[-6px] left-2 w-0 h-0 border-l-[6px] border-l-transparent border-t-[6px] border-t-[#1b1026] border-r-[6px] border-r-transparent"></div>
|
|
<div class="absolute bottom-[-3px] left-2 w-0 h-0 border-l-[4px] border-l-transparent border-t-[4px] border-t-[#e0d8f0] border-r-[4px] border-r-transparent z-10"></div>
|
|
|
|
<div class="flex flex-col items-center gap-1 min-w-[40px]">
|
|
<!-- Pixel Mood Icon -->
|
|
<svg width="24" height="24" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" class="image-pixelated">
|
|
<!-- Happy -->
|
|
<g v-if="mood.type === 'happy'">
|
|
<path d="M2 3H3V5H2V3ZM7 3H8V5H7V3Z" fill="#1b1026"/>
|
|
<path d="M2 6H3V7H2V6ZM7 6H8V7H7V6ZM3 7H7V8H3V7Z" fill="#1b1026"/>
|
|
</g>
|
|
<!-- Calm -->
|
|
<g v-else-if="mood.type === 'calm'">
|
|
<path d="M2 4H3V5H2V4ZM7 4H8V5H7V4Z" fill="#1b1026"/>
|
|
<path d="M3 7H7V8H3V7Z" fill="#1b1026"/>
|
|
</g>
|
|
<!-- Bored -->
|
|
<g v-else-if="mood.type === 'bored'">
|
|
<path d="M2 4H4V5H2V4ZM6 4H8V5H6V4Z" fill="#1b1026"/>
|
|
<path d="M3 7H7V8H3V7Z" fill="#1b1026"/>
|
|
</g>
|
|
<!-- Sad -->
|
|
<g v-else>
|
|
<path d="M2 4H3V6H2V4ZM7 4H8V6H7V4Z" fill="#1b1026"/>
|
|
<path d="M3 7H4V8H3V7ZM6 7H7V8H6V7ZM4 6H6V7H4V6Z" fill="#1b1026"/>
|
|
<path d="M2 6H3V7H2V6ZM7 6H8V7H7V6Z" fill="#2ce8f4" fill-opacity="0.5"/>
|
|
</g>
|
|
</svg>
|
|
<span class="text-[10px] font-bold font-mono leading-none">{{ mood.text }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h2 class="text-xl text-[#f6b26b] tracking-[0.2em] font-bold border-b-2 border-[#f6b26b] mb-1 leading-none pb-1 font-mono">{{ stats.name }}</h2>
|
|
<span class="text-xs text-[#8f80a0] uppercase tracking-wide font-mono mb-2">{{ translateStage(stats.class || stats.stage) }}</span>
|
|
|
|
<!-- Status Indicators -->
|
|
<div class="flex gap-2 items-center justify-center w-full mt-1 flex-wrap">
|
|
<!-- Sleeping Status -->
|
|
<div v-if="stats.isSleeping" class="px-2 py-0.5 bg-[#2b193f] border border-[#2ce8f4] text-[10px] text-[#2ce8f4] font-mono animate-pulse flex items-center gap-1" title="睡覺中">
|
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M2 2H7V3H2V2ZM2 3H3V4H2V3ZM3 4H4V5H3V4ZM4 5H5V6H4V5ZM5 6H6V7H5V6ZM6 7H7V8H6V7ZM2 7H7V8H2V7Z" fill="#2ce8f4"/>
|
|
</svg>
|
|
睡眠
|
|
</div>
|
|
|
|
<!-- Poop Status -->
|
|
<div v-if="stats.poopCount > 0" class="px-2 py-0.5 bg-[#2b193f] border border-[#d95763] text-[10px] text-[#d95763] font-mono animate-pulse flex items-center gap-1" title="需要清理便便">
|
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4 2H6V3H4V2ZM3 3H7V4H3V3ZM2 4H8V7H2V4ZM3 7H7V8H3V7Z" fill="#d95763"/>
|
|
<path d="M4 3H5V4H4V3ZM6 4H7V5H6V4Z" fill="#ff8f9c" fill-opacity="0.5"/>
|
|
</svg>
|
|
{{ stats.poopCount }}
|
|
</div>
|
|
|
|
<!-- Sick Status -->
|
|
<div v-if="stats.isSick" class="px-2 py-0.5 bg-[#2b193f] border border-[#99e550] text-[10px] text-[#99e550] font-mono animate-pulse flex items-center gap-1" title="生病了">
|
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M3 2H7V3H3V2ZM2 3H8V8H2V3ZM3 8H7V9H3V8Z" fill="#99e550"/>
|
|
<path d="M3 4H4V5H3V4ZM6 4H7V5H6V4ZM4 6H6V7H4V6Z" fill="#1b1026"/>
|
|
</svg>
|
|
生病
|
|
</div>
|
|
|
|
<!-- Dying Status -->
|
|
<div v-if="stats.dyingSeconds > 0" class="px-2 py-0.5 bg-[#2b193f] border border-[#d95763] text-[10px] text-[#d95763] font-mono animate-pulse font-bold flex items-center gap-1" title="瀕死狀態!快急救!">
|
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M2 2H8V4H2V2ZM2 4H3V6H2V4ZM7 4H8V6H7V4ZM3 6H7V7H3V6ZM4 7H6V9H4V7Z" fill="#d95763"/>
|
|
<path d="M3 3H4V4H3V3ZM6 3H7V4H6V3Z" fill="#1b1026"/>
|
|
</svg>
|
|
瀕死
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PixelFrame>
|
|
|
|
<!-- Vitals - Updated to Health, Hunger, Happiness -->
|
|
<div class="flex flex-col gap-2 px-1 mb-2">
|
|
<!-- Health (Heart) -->
|
|
<RetroResourceBar :current="stats.hp" :max="stats.maxHp" type="hp" label="生命" :segments="10">
|
|
<template #icon>
|
|
<svg width="100%" height="100%" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M2 2H4V3H2V2ZM6 2H8V3H6V2ZM1 3H2V5H1V3ZM8 3H9V5H8V3ZM2 5H3V6H2V5ZM7 5H8V6H7V5ZM3 6H4V7H3V6ZM6 6H7V7H6V6ZM4 7H6V8H4V7Z" fill="#d95763"/>
|
|
<path d="M2 3H4V4H2V3ZM6 3H8V4H6V3ZM2 4H8V5H2V4ZM3 5H7V6H3V5ZM4 6H6V7H4V6Z" fill="#ff8f9c" fill-opacity="0.5"/>
|
|
</svg>
|
|
</template>
|
|
</RetroResourceBar>
|
|
|
|
<!-- Hunger (Star) -->
|
|
<RetroResourceBar v-if="stats.hunger !== undefined" :current="stats.hunger" :max="stats.maxHunger || 100" type="energy" label="飢餓" :segments="10">
|
|
<template #icon>
|
|
<svg width="100%" height="100%" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4 1H6V3H4V1ZM1 4H3V6H1V4ZM7 4H9V6H7V4ZM4 7H6V9H4V7Z" fill="#f6b26b"/>
|
|
<path d="M4 3H6V7H4V3ZM3 4H7V6H3V4Z" fill="#ffe762"/>
|
|
</svg>
|
|
</template>
|
|
</RetroResourceBar>
|
|
|
|
<!-- Happiness (Potion) -->
|
|
<RetroResourceBar v-if="stats.happiness !== undefined" :current="stats.happiness" :max="stats.maxHappiness || 100" type="mana" label="快樂" :segments="10">
|
|
<template #icon>
|
|
<svg width="100%" height="100%" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4 1H6V3H4V1ZM3 3H7V4H3V3ZM2 4H8V8H2V4ZM3 8H7V9H3V8Z" fill="#99e550"/>
|
|
<path d="M4 2H5V3H4V2ZM3 5H5V6H3V5ZM6 6H7V7H6V6Z" fill="#e0d8f0" fill-opacity="0.8"/>
|
|
</svg>
|
|
</template>
|
|
</RetroResourceBar>
|
|
</div>
|
|
|
|
<!-- Pet Details Grid -->
|
|
<PixelFrame class="flex-shrink-0 mt-1" variant="inset">
|
|
<div class="flex flex-col gap-2 p-1">
|
|
<!-- Basic Info -->
|
|
<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-[10px] uppercase text-[#8f80a0]">
|
|
<div class="flex flex-col border-r border-[#4a3b5e] pr-2">
|
|
<span class="text-[#4a3b5e] font-mono">等級</span>
|
|
<span class="text-[#e0d8f0] font-mono tracking-wide">Lv {{ stats.lvl }}</span>
|
|
</div>
|
|
<div class="flex flex-col pl-2">
|
|
<span class="text-[#4a3b5e] font-mono">世代</span>
|
|
<span class="text-[#e0d8f0] font-mono tracking-wide">{{ stats.generation }}</span>
|
|
</div>
|
|
<div class="flex flex-col border-r border-[#4a3b5e] border-t border-t-[#4a3b5e] pr-2 pt-2">
|
|
<span class="text-[#4a3b5e] font-mono">年齡</span>
|
|
<span class="text-[#e0d8f0] font-mono tracking-wide">{{ stats.age }}</span>
|
|
</div>
|
|
|
|
<!-- Height (Text Label) -->
|
|
<div class="flex flex-col border-t border-[#4a3b5e] pt-2 pl-2">
|
|
<span class="text-[#4a3b5e] font-mono">身高</span>
|
|
<span class="text-[#e0d8f0] font-mono tracking-wide">{{ stats.height }}</span>
|
|
</div>
|
|
|
|
<!-- Weight (Text Label) -->
|
|
<div class="flex flex-col border-t border-[#4a3b5e] pt-2 col-span-2">
|
|
<span class="text-[#4a3b5e] font-mono">體重</span>
|
|
<span class="text-[#e0d8f0] font-mono tracking-wide">{{ stats.weight }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Core Stats -->
|
|
<div class="grid grid-cols-2 gap-x-4 gap-y-2 pt-2 border-t border-[#4a3b5e]">
|
|
<div class="flex justify-between items-center px-1">
|
|
<span class="text-[10px] text-[#f6b26b] font-mono">力量</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono">{{ Math.floor(stats.str || 0) }}</span>
|
|
</div>
|
|
<div class="flex justify-between items-center px-1">
|
|
<span class="text-[10px] text-[#2ce8f4] font-mono">智力</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono">{{ Math.floor(stats.int || 0) }}</span>
|
|
</div>
|
|
<div class="flex justify-between items-center px-1">
|
|
<span class="text-[10px] text-[#99e550] font-mono">敏捷</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono">{{ Math.floor(stats.dex || 0) }}</span>
|
|
</div>
|
|
<div class="flex justify-between items-center px-1">
|
|
<span class="text-[10px] text-[#d95763] font-mono">運氣</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono">{{ Math.floor(stats.luck || 0) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Combat Stats -->
|
|
<div class="grid grid-cols-3 gap-x-2 gap-y-2 pt-2 border-t border-[#4a3b5e]">
|
|
<div class="flex flex-col items-center">
|
|
<span class="text-[8px] text-[#d95763] font-mono mb-0.5">攻擊</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono font-bold">{{ Math.floor(stats.atk || 0) }}</span>
|
|
</div>
|
|
<div class="flex flex-col items-center">
|
|
<span class="text-[8px] text-[#f6b26b] font-mono mb-0.5">防禦</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono font-bold">{{ Math.floor(stats.def || 0) }}</span>
|
|
</div>
|
|
<div class="flex flex-col items-center">
|
|
<span class="text-[8px] text-[#2ce8f4] font-mono mb-0.5">速度</span>
|
|
<span class="text-[10px] text-[#e0d8f0] font-mono font-bold">{{ Math.floor(stats.spd || 0) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PixelFrame>
|
|
|
|
<!-- Fate Display (Enhanced) -->
|
|
<div v-if="fateInfo" class="flex flex-col gap-2 mt-2 mb-6 px-1 flex-shrink-0">
|
|
<PixelFrame class="relative overflow-visible" variant="inset" title="命運">
|
|
<!-- Tier Badge -->
|
|
<div class="absolute -top-2 -right-2 z-10">
|
|
<div
|
|
class="px-2 py-0.5 text-[8px] font-bold font-mono border-2 shadow-lg"
|
|
:style="{
|
|
backgroundColor: fateInfo.color,
|
|
borderColor: fateInfo.color,
|
|
color: '#000',
|
|
boxShadow: `0 0 10px ${fateInfo.color}, 0 0 20px ${fateInfo.color}40`
|
|
}"
|
|
>
|
|
{{ fateInfo.tierName }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-2 flex gap-2">
|
|
<!-- Pixel Icon with Glow -->
|
|
<div
|
|
class="w-12 h-12 flex-shrink-0 bg-[#0f0816] border-2 flex items-center justify-center relative overflow-hidden"
|
|
:style="{
|
|
borderColor: fateInfo.color,
|
|
boxShadow: `inset 0 0 10px ${fateInfo.color}40, 0 0 15px ${fateInfo.color}60`
|
|
}"
|
|
>
|
|
<!-- Pixel art background pattern -->
|
|
<div class="absolute inset-0 opacity-20" style="background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.1) 2px, rgba(255,255,255,0.1) 4px)"></div>
|
|
|
|
<!-- Icon based on tier -->
|
|
<div class="relative text-2xl animate-pulse" :style="{ filter: `drop-shadow(0 0 4px ${fateInfo.color})` }">
|
|
<Sparkles v-if="fateInfo.tier === 'SSR'" :size="32" :color="fateInfo.color" />
|
|
<Star v-else-if="fateInfo.tier === 'SR'" :size="28" :color="fateInfo.color" />
|
|
<Gem v-else-if="fateInfo.tier === 'R'" :size="24" :color="fateInfo.color" />
|
|
<Circle v-else-if="fateInfo.tier === 'N'" :size="20" :color="fateInfo.color" />
|
|
<Leaf v-else :size="16" :color="fateInfo.color" />
|
|
</div>
|
|
|
|
<!-- Scanlines -->
|
|
<div class="absolute inset-0 bg-[linear-gradient(rgba(0,0,0,0)_50%,rgba(0,0,0,0.3)_50%)] bg-[length:100%_4px] pointer-events-none"></div>
|
|
</div>
|
|
|
|
<!-- Fate Info -->
|
|
<div class="flex-1 min-w-0">
|
|
<!-- Name with glow effect -->
|
|
<div
|
|
class="text-sm font-bold font-mono mb-1 truncate"
|
|
:style="{
|
|
color: fateInfo.color,
|
|
textShadow: `0 0 8px ${fateInfo.color}, 0 0 12px ${fateInfo.color}80`
|
|
}"
|
|
>
|
|
{{ stats.fate }}
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<p class="text-[9px] text-[#8f80a0] leading-tight mb-2 line-clamp-2 font-mono">
|
|
{{ fateInfo.description }}
|
|
</p>
|
|
|
|
<!-- Buffs -->
|
|
<div v-if="fateInfo.buffsList && fateInfo.buffsList.length > 0" class="flex flex-wrap gap-1">
|
|
<div
|
|
v-for="(buff, index) in fateInfo.buffsList"
|
|
:key="index"
|
|
class="text-[8px] px-1 py-0.5 border bg-[#0f0816] font-mono"
|
|
:style="{ borderColor: fateInfo.color + '60', color: fateInfo.color }"
|
|
:title="buff"
|
|
>
|
|
{{ buff }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PixelFrame>
|
|
</div>
|
|
|
|
<!-- Gold & Inventory -->
|
|
<div class="mt-auto px-1 pb-4 flex-shrink-0 flex gap-2">
|
|
<!-- Gold Display -->
|
|
<div class="flex-1">
|
|
<RetroCounter :value="stats.gold || 0" color="#ffe762">
|
|
<template #icon>
|
|
<svg width="100%" height="100%" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M3 2H7V3H3V2ZM2 3H8V7H2V3ZM3 7H7V8H3V7Z" fill="#ffe762"/>
|
|
<path d="M4 3H5V6H4V3ZM6 3H7V6H6V3Z" fill="#b48b38"/>
|
|
</svg>
|
|
</template>
|
|
</RetroCounter>
|
|
</div>
|
|
|
|
<!-- Inventory Button (Styled like Action Buttons) -->
|
|
<button
|
|
@click="$emit('openInventory')"
|
|
class="w-10 bg-[#1b1026] border border-[#4a3b5e] hover:bg-[#2b193f] active:translate-y-0.5 group flex flex-col items-center justify-center gap-1 py-1 transition-all relative overflow-hidden"
|
|
title="背包"
|
|
>
|
|
<!-- Pixel Icon -->
|
|
<div class="w-4 h-4 relative z-10">
|
|
<svg width="100%" height="100%" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M3 1H7V3H3V1ZM1 3H9V9H1V3ZM4 4H6V5H4V4Z" fill="#8f80a0" class="group-hover:fill-[#e0d8f0] transition-colors"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Corner Accents -->
|
|
<div class="absolute top-0 left-0 w-1 h-1 border-t border-l border-[#4a3b5e] opacity-50"></div>
|
|
<div class="absolute top-0 right-0 w-1 h-1 border-t border-r border-[#4a3b5e] opacity-50"></div>
|
|
<div class="absolute bottom-0 left-0 w-1 h-1 border-b border-l border-[#4a3b5e] opacity-50"></div>
|
|
<div class="absolute bottom-0 right-0 w-1 h-1 border-b border-r border-[#4a3b5e] opacity-50"></div>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { Trophy, Trash2, Sparkles, Star, Gem, Circle, Leaf } from 'lucide-vue-next';
|
|
import PixelFrame from './PixelFrame.vue';
|
|
import RetroResourceBar from './RetroResourceBar.vue';
|
|
import RetroCounter from './RetroCounter.vue';
|
|
import PixelAvatar from './PixelAvatar.vue';
|
|
import { FATE_TIERS, FATES } from '../../../data/fates.js';
|
|
|
|
type EntityStats = any;
|
|
|
|
interface Props {
|
|
stats: EntityStats;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
defineEmits(['openAchievements', 'deletePet', 'openInventory']);
|
|
|
|
// Determine mood based on happiness
|
|
const mood = computed(() => {
|
|
const happiness = props.stats.happiness || 0;
|
|
if (happiness >= 80) return { icon: '😄', text: '開心', type: 'happy' };
|
|
if (happiness >= 50) return { icon: '🙂', text: '平靜', type: 'calm' };
|
|
if (happiness >= 20) return { icon: '😐', text: '無聊', type: 'bored' };
|
|
return { icon: '😭', text: '難過', type: 'sad' };
|
|
});
|
|
|
|
// Helper to translate stage
|
|
function translateStage(stage: string): string {
|
|
if (!stage) return '';
|
|
const map: Record<string, string> = {
|
|
'egg': '蛋',
|
|
'baby': '幼年期',
|
|
'child': '成長期',
|
|
'adult': '成熟期',
|
|
'mythic': '神話期',
|
|
'EGG': '蛋',
|
|
'BABY': '幼年期',
|
|
'CHILD': '成長期',
|
|
'ADULT': '成熟期',
|
|
'MYTHIC': '神話期'
|
|
};
|
|
return map[stage] || stage;
|
|
}
|
|
|
|
// Process fate information
|
|
const fateInfo = computed(() => {
|
|
if (!props.stats.fate) return null;
|
|
|
|
// Find fate data
|
|
const fateData = FATES.find(f => f.name === props.stats.fate);
|
|
if (!fateData) return null;
|
|
|
|
const tierData = FATE_TIERS[fateData.tier as keyof typeof FATE_TIERS];
|
|
|
|
// Format buffs into readable list
|
|
const buffsList: string[] = [];
|
|
if (fateData.buffs) {
|
|
for (const [key, value] of Object.entries(fateData.buffs)) {
|
|
let displayValue = value as number;
|
|
let prefix = '+';
|
|
|
|
// Handle percentage buffs
|
|
if (typeof value === 'number') {
|
|
if (value < 0) {
|
|
prefix = '';
|
|
}
|
|
if (Math.abs(value) < 1 && value !== 0) {
|
|
displayValue = Math.round(value * 100);
|
|
buffsList.push(`${prefix}${displayValue}% ${formatBuffKey(key)}`);
|
|
} else {
|
|
buffsList.push(`${prefix}${displayValue} ${formatBuffKey(key)}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
tier: fateData.tier,
|
|
tierName: tierData.name,
|
|
color: tierData.color,
|
|
description: fateData.description,
|
|
buffsList
|
|
};
|
|
});
|
|
|
|
// Helper to format buff keys
|
|
function formatBuffKey(key: string): string {
|
|
const keyMap: Record<string, string> = {
|
|
luck: '運氣',
|
|
attack: '攻擊',
|
|
defense: '防禦',
|
|
speed: '速度',
|
|
strGain: '力量成長',
|
|
intGain: '智力成長',
|
|
dexGain: '敏捷成長',
|
|
healthRegen: '健康恢復',
|
|
happinessRecovery: '快樂恢復',
|
|
hungerDecay: '飢餓速度',
|
|
sicknessReduction: '生病機率↓',
|
|
badEventReduction: '壞事機率↓',
|
|
resourceGain: '資源獲得',
|
|
dropRate: '掉寶率',
|
|
gameSuccessRate: '遊戲成功率',
|
|
miniGameBonus: '小遊戲獎勵',
|
|
breedingSuccess: '繁殖成功率'
|
|
};
|
|
return keyMap[key] || key;
|
|
}
|
|
</script>
|