fix: god system
This commit is contained in:
parent
086a7b796a
commit
63ee9b71ef
|
|
@ -0,0 +1,215 @@
|
|||
<template>
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm" @click.self="$emit('close')">
|
||||
<div class="relative w-full max-w-md bg-[#1b1026] border-4 border-[#4a3b5e] shadow-2xl p-4 m-4 max-h-[90vh] overflow-y-auto custom-scrollbar">
|
||||
|
||||
<!-- Close Button -->
|
||||
<button
|
||||
@click="$emit('close')"
|
||||
class="absolute top-2 right-2 text-[#8f80a0] hover:text-white"
|
||||
>
|
||||
<X :size="24" />
|
||||
</button>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-[#f6b26b] font-mono tracking-wider">守護神明</h2>
|
||||
<div class="h-1 w-24 bg-[#f6b26b] mx-auto mt-2"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentDeity && currentStage" class="flex flex-col gap-6">
|
||||
<!-- Deity Portrait & Info -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-32 h-32 bg-[#0f0816] border-4 border-[#f6b26b] rounded-full flex items-center justify-center mb-4 relative overflow-hidden group">
|
||||
<!-- Glow Effect -->
|
||||
<div class="absolute inset-0 bg-[#f6b26b] opacity-10 animate-pulse"></div>
|
||||
|
||||
<!-- Icon Placeholder (Replace with actual image later) -->
|
||||
<div class="text-6xl">{{ getDeityIcon(String(currentDeity.id)) }}</div>
|
||||
|
||||
<!-- Stage Badge -->
|
||||
<div class="absolute bottom-0 bg-[#f6b26b] text-[#1b1026] text-xs font-bold px-3 py-1 rounded-full border-2 border-[#1b1026]">
|
||||
Lv.{{ currentStage.level }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold text-[#e0d8f0]">{{ currentStage.name }}</h3>
|
||||
<p class="text-[#f6b26b] text-sm font-mono mb-2">{{ currentStage.title }}</p>
|
||||
<p class="text-[#8f80a0] text-xs text-center max-w-[80%]">{{ currentStage.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Evolution Progress -->
|
||||
<div class="bg-[#0f0816] p-4 border-2 border-[#4a3b5e] rounded-lg">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-[#e0d8f0] text-sm font-bold">進化進度</span>
|
||||
<span class="text-[#8f80a0] text-xs">{{ currentDeityState.exp }} / {{ nextStageExp }} EXP</span>
|
||||
</div>
|
||||
<div class="w-full h-4 bg-[#2b193f] rounded-full overflow-hidden border border-[#4a3b5e]">
|
||||
<div
|
||||
class="h-full bg-gradient-to-r from-[#f6b26b] to-[#d95763] transition-all duration-500"
|
||||
:style="{ width: `${expPercentage}%` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Evolve Button -->
|
||||
<button
|
||||
v-if="canEvolve"
|
||||
@click="$emit('evolve')"
|
||||
class="w-full mt-4 py-2 bg-[#f6b26b] text-[#1b1026] font-bold rounded hover:bg-[#ffe762] transition-colors animate-pulse"
|
||||
>
|
||||
✨ 立即進化 ✨
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Active Buffs -->
|
||||
<div class="bg-[#0f0816] p-4 border-2 border-[#4a3b5e] rounded-lg">
|
||||
<h4 class="text-[#2ce8f4] text-sm font-bold mb-3 border-b border-[#2ce8f4]/30 pb-1">神力加成</h4>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div v-for="(value, key) in currentStage.buffs" :key="key" class="flex items-center gap-2 text-xs text-[#e0d8f0]">
|
||||
<span class="w-1.5 h-1.5 bg-[#2ce8f4] rounded-full"></span>
|
||||
{{ formatBuff(key, value) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quests -->
|
||||
<div class="bg-[#0f0816] p-4 border-2 border-[#4a3b5e] rounded-lg">
|
||||
<h4 class="text-[#99e550] text-sm font-bold mb-3 border-b border-[#99e550]/30 pb-1">進化任務</h4>
|
||||
<div class="space-y-3">
|
||||
<div v-for="quest in currentDeity.quests" :key="quest.id" class="relative">
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<span class="text-[#e0d8f0] text-xs">{{ quest.description }}</span>
|
||||
<span class="text-[#99e550] text-xs font-mono">
|
||||
{{ getQuestProgress(quest.id) }} / {{ quest.target }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full h-1.5 bg-[#2b193f] rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-[#99e550]"
|
||||
:style="{ width: `${Math.min(100, (getQuestProgress(quest.id) / quest.target) * 100)}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
@click="$emit('divination')"
|
||||
class="py-3 bg-[#2b193f] border-2 border-[#2ce8f4] text-[#2ce8f4] font-bold rounded hover:bg-[#3d2459] transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Sparkles :size="18" />
|
||||
{{ currentDeity.origin === 'western' ? '塔羅占卜' : '每日求籤' }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="$emit('switch')"
|
||||
class="py-3 bg-[#2b193f] border-2 border-[#8f80a0] text-[#8f80a0] font-bold rounded hover:bg-[#3d2459] transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<RefreshCw :size="18" />
|
||||
更換神明
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-12 text-[#8f80a0]">
|
||||
<p>尚未供奉任何神明</p>
|
||||
<button
|
||||
@click="$emit('encounter')"
|
||||
class="mt-4 px-6 py-2 bg-[#f6b26b] text-[#1b1026] font-bold rounded hover:bg-[#ffe762]"
|
||||
>
|
||||
尋找緣分
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { X, Sparkles, RefreshCw } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps<{
|
||||
currentDeity: any;
|
||||
currentDeityState: any;
|
||||
currentStage: any;
|
||||
}>();
|
||||
|
||||
defineEmits(['close', 'evolve', 'divination', 'switch', 'encounter']);
|
||||
|
||||
const nextStageExp = computed(() => {
|
||||
if (!props.currentDeity || !props.currentStage) return 100;
|
||||
const nextStage = props.currentDeity.stages.find((s: any) => s.level === props.currentStage.level + 1);
|
||||
return nextStage ? nextStage.requiredExp : props.currentStage.requiredExp;
|
||||
});
|
||||
|
||||
const expPercentage = computed(() => {
|
||||
if (!props.currentDeityState) return 0;
|
||||
return Math.min(100, (props.currentDeityState.exp / nextStageExp.value) * 100);
|
||||
});
|
||||
|
||||
const canEvolve = computed(() => {
|
||||
if (!props.currentDeity || !props.currentDeityState) return false;
|
||||
// Check max level
|
||||
if (props.currentStage.level >= props.currentDeity.stages.length) return false;
|
||||
|
||||
// Check EXP
|
||||
if (props.currentDeityState.exp < nextStageExp.value) return false;
|
||||
|
||||
// Check Quests
|
||||
return props.currentDeity.quests.every((q: any) => {
|
||||
return (props.currentDeityState.quests[q.id] || 0) >= q.target;
|
||||
});
|
||||
});
|
||||
|
||||
function getQuestProgress(questId: string) {
|
||||
return props.currentDeityState?.quests?.[questId] || 0;
|
||||
}
|
||||
|
||||
function formatBuff(key: string, value: any) {
|
||||
const map: Record<string, string> = {
|
||||
gameSuccessRate: '遊戲成功率',
|
||||
sicknessReduction: '生病機率',
|
||||
happinessRecovery: '快樂恢復',
|
||||
dropRate: '掉寶率',
|
||||
resourceGain: '資源獲得',
|
||||
intGain: '智力成長',
|
||||
miniGameBonus: '小遊戲獎勵',
|
||||
healthRecovery: '健康恢復',
|
||||
badEventReduction: '壞事機率',
|
||||
defense: '防禦力',
|
||||
attack: '攻擊力',
|
||||
luck: '運氣',
|
||||
str: '力量',
|
||||
int: '智力',
|
||||
dex: '敏捷',
|
||||
health: '最大健康',
|
||||
sicknessImmune: '免疫生病'
|
||||
};
|
||||
|
||||
const label = map[key] || key;
|
||||
|
||||
if (key === 'sicknessImmune') return label;
|
||||
|
||||
const isPercent = typeof value === 'number' && value < 1 && value > -1;
|
||||
const valStr = isPercent ? `${Math.round(value * 100)}%` : value;
|
||||
const prefix = value > 0 ? '+' : '';
|
||||
|
||||
return `${label} ${prefix}${valStr}`;
|
||||
}
|
||||
|
||||
function getDeityIcon(id: string) {
|
||||
const icons: Record<string, string> = {
|
||||
mazu: '🌊',
|
||||
earthgod: '🏮',
|
||||
yuelao: '❤️',
|
||||
wenchang: '📜',
|
||||
guanyin: '🪷',
|
||||
athena: '🦉'
|
||||
};
|
||||
return icons[id] || '✨';
|
||||
}
|
||||
</script>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="flex flex-col h-full gap-4">
|
||||
<!-- Header: Deity System Title -->
|
||||
<div class="text-xl font-bold tracking-widest text-[#2ce8f4] border-b-2 border-[#4a3b5e] pb-2">
|
||||
[GOD SYSTEM] 神明系統
|
||||
神明系統
|
||||
</div>
|
||||
|
||||
<!-- Top Action Buttons Grid -->
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
{{ action.label }}
|
||||
</PixelButton>
|
||||
<PixelButton :variant="activeTab === 'LIST' ? 'primary' : 'secondary'" @click="activeTab = 'LIST'" class="text-xs md:text-sm">
|
||||
[LIST] 神明列表
|
||||
神明列表
|
||||
</PixelButton>
|
||||
</div>
|
||||
|
||||
|
|
@ -31,39 +31,164 @@
|
|||
</div>
|
||||
|
||||
<!-- --- VIEW: PRAY --- -->
|
||||
<div v-if="activeTab === 'PRAY'" class="flex flex-col items-center justify-center h-full gap-6">
|
||||
<div class="transform scale-150 mb-4">
|
||||
<PixelAvatar :deityId="currentDeity" />
|
||||
</div>
|
||||
<h2 class="text-2xl text-[#f6b26b] font-bold">{{ activeDeity.title }} {{ activeDeity.name }}</h2>
|
||||
|
||||
<div class="w-full max-w-md">
|
||||
<div class="flex justify-between text-xs text-[#8f80a0] mb-1">
|
||||
<span>FAVOR (好感度)</span>
|
||||
<span>{{ activeDeity.favor }}/{{ activeDeity.maxFavor }}</span>
|
||||
<div v-if="activeTab === 'PRAY'" class="flex flex-col h-full gap-4 overflow-y-auto custom-scrollbar">
|
||||
<!-- Deity Avatar & Basic Info -->
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="relative">
|
||||
<PixelAvatar
|
||||
:deityId="currentDeity"
|
||||
:stageLevel="activeDeityState?.stageLevel || 1"
|
||||
class="w-32 h-32"
|
||||
/>
|
||||
</div>
|
||||
<div class="h-4 bg-[#2b193f] border border-[#4a3b5e] rounded-full overflow-hidden relative">
|
||||
|
||||
<div class="text-center">
|
||||
<h2 class="text-2xl text-[#f6b26b] font-bold">{{ activeStage?.name || activeDeity.name }}</h2>
|
||||
<div class="text-[#99e550] text-sm tracking-wider mb-1">【{{ activeStage?.title || activeDeity.personality }}】</div>
|
||||
<p class="text-[#8f80a0] text-xs max-w-md italic leading-relaxed">"{{ activeStage?.description }}"</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
||||
<!-- Max Level Badge -->
|
||||
<div v-if="isMaxLevel" class="text-center mb-3">
|
||||
<span class="text-[#f6b26b] font-bold text-sm border border-[#f6b26b] px-3 py-1 rounded bg-[#2b193f] inline-block">
|
||||
✨ 已達最高階 ✨
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Favor -->
|
||||
<div>
|
||||
<div class="flex justify-between text-xs text-[#8f80a0] mb-1">
|
||||
<span>好感度</span>
|
||||
<span>{{ activeDeityFavor }}/100</span>
|
||||
</div>
|
||||
<div class="h-2 bg-[#2b193f] border border-[#4a3b5e] rounded-full overflow-hidden relative">
|
||||
<div
|
||||
class="h-full bg-[#d95763] transition-all duration-500"
|
||||
:style="{ width: `${activeDeityFavor}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Stage Buffs -->
|
||||
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
||||
<div class="text-[#f6b26b] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
||||
【神力】
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div
|
||||
class="h-full bg-[#d95763] transition-all duration-500"
|
||||
:style="{ width: `${(activeDeity.favor / activeDeity.maxFavor) * 100}%` }"
|
||||
></div>
|
||||
v-for="(value, key) in currentStageBuffs"
|
||||
:key="key"
|
||||
class="bg-[#0f0816] border border-[#4a3b5e] px-2 py-1 rounded"
|
||||
>
|
||||
<span class="text-[#99e550]">{{ formatBuffKey(key) }}:</span>
|
||||
<span class="text-[#e0d8f0] ml-1">{{ formatBuffValue(value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PixelButton class="w-48 py-4 text-lg animate-pulse" @click="$emit('addFavor', 10)">
|
||||
🙏 PRAY (祈福)
|
||||
</PixelButton>
|
||||
<p class="text-xs text-[#8f80a0]">Increases favor with {{ activeDeity.name }}</p>
|
||||
<!-- Quests for Current Stage -->
|
||||
<div v-if="!isMaxLevel && currentStageQuests.length > 0" class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
||||
<div class="text-[#2ce8f4] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
||||
【修行】當前階段任務
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="quest in currentStageQuests"
|
||||
:key="quest.id"
|
||||
class="bg-[#0f0816] border border-[#4a3b5e] p-2 rounded"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<span class="text-[#e0d8f0] text-xs">{{ quest.description }}</span>
|
||||
<span
|
||||
class="text-xs font-bold"
|
||||
:class="questProgress(quest.id) >= quest.target ? 'text-[#99e550]' : 'text-[#8f80a0]'"
|
||||
>
|
||||
{{ questProgress(quest.id) }}/{{ quest.target }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="h-1 bg-[#2b193f] rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full transition-all duration-500"
|
||||
:class="questProgress(quest.id) >= quest.target ? 'bg-[#99e550]' : 'bg-[#8f80a0]'"
|
||||
:style="{ width: `${Math.min(100, (questProgress(quest.id) / quest.target) * 100)}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Evolve Button (when all quests completed) -->
|
||||
<div v-if="canEvolveDeity" class="mt-3 text-center">
|
||||
<PixelButton
|
||||
class="w-full py-2 text-sm bg-[#2ce8f4] hover:bg-[#1ad4e0] animate-pulse"
|
||||
@click="handleEvolve"
|
||||
>
|
||||
✨ 進化到下一階段 ✨
|
||||
</PixelButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lot Types -->
|
||||
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
||||
<div class="text-[#ffe762] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
||||
【求籤】
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="lotType in activeDeity.lotTypes"
|
||||
:key="lotType"
|
||||
class="bg-[#0f0816] border border-[#4a3b5e] px-3 py-1 rounded text-xs text-[#e0d8f0]"
|
||||
>
|
||||
{{ formatLotType(lotType) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deity Dialogue (Random One) -->
|
||||
<div class="bg-[#1b1026] border border-[#4a3b5e] p-3 rounded">
|
||||
<div class="text-[#d95763] text-sm font-bold mb-2 border-b border-[#4a3b5e] pb-1">
|
||||
【神諭】
|
||||
</div>
|
||||
<div class="bg-[#0f0816] border-l-2 border-[#d95763] px-3 py-2 text-sm text-[#e0d8f0] italic text-center">
|
||||
"{{ randomDialogue }}"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pray Button -->
|
||||
<div class="flex flex-col items-center gap-2 mt-2 mb-4">
|
||||
<PixelButton
|
||||
class="w-48 py-3 text-base"
|
||||
:class="{ 'animate-pulse': canPray }"
|
||||
:disabled="!canPray"
|
||||
@click="$emit('addFavor', 10)"
|
||||
>
|
||||
🙏 祈福 ({{ dailyPrayerCount || 0 }}/3)
|
||||
</PixelButton>
|
||||
<p v-if="(dailyPrayerCount || 0) >= 3" class="text-xs text-[#8f80a0]">今日祈福次數已用完</p>
|
||||
<p v-else-if="activeDeityFavor >= 100" class="text-xs text-[#8f80a0]">好感度已滿</p>
|
||||
<p v-else class="text-xs text-[#8f80a0]">增加好感度與修煉值 (剩餘 {{ 3 - (dailyPrayerCount || 0) }} 次)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- --- VIEW: JIAOBEI (Free Toss) --- -->
|
||||
<!-- --- VIEW: JIAOBEI (Free Toss) --- -->
|
||||
<div v-else-if="activeTab === 'JIAOBEI'" class="flex flex-col items-center justify-center h-full gap-8">
|
||||
<h3 class="text-[#f6b26b] text-lg uppercase tracking-widest">Moon Block Divination</h3>
|
||||
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
|
||||
<h3 class="text-[#f6b26b] text-lg uppercase tracking-widest">擲筊問事</h3>
|
||||
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
|
||||
|
||||
<div v-if="lastResult && !isTossing" class="text-xl font-bold text-[#e0d8f0]">
|
||||
{{ getJiaobeiResultText(lastResult) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<PixelButton @click="handleToss(false)" :disabled="isTossing" class="w-40">
|
||||
{{ isTossing ? 'TOSSING...' : 'TOSS BLOCKS' }}
|
||||
{{ isTossing ? '擲筊中...' : '開始擲筊' }}
|
||||
</PixelButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -74,12 +199,12 @@
|
|||
<!-- Phase: Idle -->
|
||||
<template v-if="lotPhase === LotPhase.Idle">
|
||||
<Scroll :size="64" class="text-[#ffe762] mb-4" />
|
||||
<h3 class="text-xl text-[#e0d8f0] mb-2">Draw a Fortune Lot</h3>
|
||||
<h3 class="text-xl text-[#e0d8f0] mb-2">誠心求籤</h3>
|
||||
<p class="text-sm text-[#8f80a0] max-w-xs mb-6">
|
||||
Shake the container to draw a stick, then verify it with 3 consecutive Saint Cups.
|
||||
搖動籤筒求出一支籤,並需連續三個聖杯確認。
|
||||
</p>
|
||||
<PixelButton @click="handleDrawLot" class="w-48">
|
||||
DRAW LOT
|
||||
開始求籤
|
||||
</PixelButton>
|
||||
</template>
|
||||
|
||||
|
|
@ -90,17 +215,17 @@
|
|||
...
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[#f6b26b] tracking-widest">SHAKING...</span>
|
||||
<span class="text-[#f6b26b] tracking-widest">搖籤中...</span>
|
||||
</div>
|
||||
|
||||
<!-- Phase: Verify -->
|
||||
<template v-else-if="lotPhase === LotPhase.PendingVerify || lotPhase === LotPhase.Verifying">
|
||||
<div class="text-2xl font-bold text-[#e0d8f0] border-2 border-[#f6b26b] px-4 py-2 mb-4 bg-[#2b193f]">
|
||||
LOT #{{ drawnLotNumber }}
|
||||
{{ drawnLot?.no }}
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#8f80a0] mb-4">
|
||||
Verify with 3 Consecutive Saint Cups
|
||||
需連續三個聖杯確認
|
||||
</p>
|
||||
|
||||
<div class="flex gap-2 mb-6 justify-center">
|
||||
|
|
@ -116,7 +241,7 @@
|
|||
|
||||
<div class="mt-8">
|
||||
<PixelButton @click="handleToss(true)" :disabled="isTossing" class="w-40">
|
||||
VERIFY ({{ saintCupCount }}/3)
|
||||
擲筊確認 ({{ saintCupCount }}/3)
|
||||
</PixelButton>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -124,17 +249,17 @@
|
|||
<!-- Phase: Failed -->
|
||||
<template v-else-if="lotPhase === LotPhase.Failed">
|
||||
<div class="text-[#d95763] text-4xl mb-4">✖</div>
|
||||
<h3 class="text-lg text-[#d95763] mb-2">Not a Saint Cup</h3>
|
||||
<h3 class="text-lg text-[#d95763] mb-2">笑杯/陰杯</h3>
|
||||
<p class="text-sm text-[#8f80a0] mb-6">
|
||||
The deity indicates this is not the right lot.<br/>Please draw again.
|
||||
神明指示此籤不對,<br/>請重新求籤。
|
||||
</p>
|
||||
<PixelButton @click="resetLot" variant="danger">
|
||||
TRY AGAIN
|
||||
重新求籤
|
||||
</PixelButton>
|
||||
</template>
|
||||
|
||||
<!-- Phase: Success - Detailed Result -->
|
||||
<div v-else-if="lotPhase === LotPhase.Success" class="w-full h-full overflow-y-auto custom-scrollbar p-2">
|
||||
<div v-else-if="lotPhase === LotPhase.Result && drawnLot" class="w-full h-full overflow-y-auto custom-scrollbar p-2">
|
||||
<PixelFrame class="bg-[#1b1026] border-4 border-[#f6b26b] relative shadow-[0_0_20px_rgba(246,178,107,0.3)]">
|
||||
<!-- Header -->
|
||||
<div class="text-center border-b-2 border-[#4a3b5e] pb-3 mb-3 bg-[#231533] p-2">
|
||||
|
|
@ -144,14 +269,14 @@
|
|||
<Sparkles :size="16" />
|
||||
</div>
|
||||
<div class="text-[#f6b26b] text-xl md:text-3xl font-bold tracking-widest mt-2 font-serif">
|
||||
【{{ LOT_RESULT_DATA.number }}】 {{ LOT_RESULT_DATA.level }}
|
||||
【{{ drawnLot.no }}】 {{ drawnLot.grade }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Poem (Block) -->
|
||||
<div class="bg-[#2b193f] p-4 text-center mb-4 border-l-4 border-[#f6b26b] mx-2 shadow-inner">
|
||||
<div v-for="(line, i) in LOT_RESULT_DATA.poem" :key="i" class="text-lg md:text-xl text-[#e0d8f0] tracking-[0.2em] leading-loose font-serif drop-shadow-md">
|
||||
{{ line }}
|
||||
<div class="text-lg md:text-xl text-[#e0d8f0] tracking-[0.2em] leading-loose font-serif drop-shadow-md whitespace-pre-line">
|
||||
{{ drawnLot.poem1 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -160,33 +285,43 @@
|
|||
<!-- Meaning -->
|
||||
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
||||
<span class="text-[#f6b26b] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
||||
【解曰】 Meaning
|
||||
【聖意】 Meaning
|
||||
</span>
|
||||
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ LOT_RESULT_DATA.meaning }}</p>
|
||||
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ drawnLot.meaning }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Interpretation -->
|
||||
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
||||
<span class="text-[#99e550] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
||||
【解籤】 Interpretation
|
||||
【解曰】 Interpretation
|
||||
</span>
|
||||
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ LOT_RESULT_DATA.interpretation }}</p>
|
||||
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ drawnLot.explanation }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Oracle -->
|
||||
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
||||
<span class="text-[#2ce8f4] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
||||
【仙機】 Oracle
|
||||
</span>
|
||||
<div class="text-[#e0d8f0] text-sm leading-relaxed mt-1 whitespace-pre-wrap">
|
||||
{{ drawnLot.oracle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Story -->
|
||||
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
|
||||
<span class="text-[#2ce8f4] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
|
||||
【典故】 Story: {{ LOT_RESULT_DATA.storyTitle }}
|
||||
【典故】 Story
|
||||
</span>
|
||||
<div class="text-[#8f80a0] text-xs leading-relaxed mt-1">
|
||||
{{ LOT_RESULT_DATA.story }}
|
||||
{{ getFirstStory(drawnLot.story) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-center mb-2">
|
||||
<PixelButton @click="resetLot" class="w-full md:w-auto px-8 py-3">
|
||||
收入背包 (KEEP LOT)
|
||||
收下
|
||||
</PixelButton>
|
||||
</div>
|
||||
</PixelFrame>
|
||||
|
|
@ -195,7 +330,7 @@
|
|||
|
||||
<!-- --- VIEW: LIST/SWITCH --- -->
|
||||
<div v-else-if="activeTab === 'LIST' || activeTab === 'VERIFY'" class="flex flex-col gap-4">
|
||||
<div class="text-xs text-[#8f80a0] uppercase mb-2">▼ [SWITCH] 切換神明</div>
|
||||
<div class="text-xs text-[#8f80a0] uppercase mb-2">▼ 切換神明</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
|
|
@ -206,7 +341,7 @@
|
|||
:class="currentDeity === deity.id ? 'border-[#99e550] bg-[#2b193f]' : 'border-[#4a3b5e] bg-[#0f0816] hover:bg-[#150c1f]'"
|
||||
>
|
||||
<div class="w-10 h-10 relative">
|
||||
<PixelAvatar :deityId="deity.id" />
|
||||
<PixelAvatar :deityId="deity.id" :stageLevel="getDeityStageLevel(deity.id)" />
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-bold" :class="currentDeity === deity.id ? 'text-[#99e550]' : 'text-[#e0d8f0]'">
|
||||
|
|
@ -231,16 +366,20 @@ import PixelButton from './PixelButton.vue';
|
|||
import PixelAvatar from './PixelAvatar.vue';
|
||||
import PixelFrame from './PixelFrame.vue';
|
||||
import JiaobeiBlocks from './JiaobeiBlocks.vue';
|
||||
import guanyinLots from '../../../guanyin_100_lots.json';
|
||||
|
||||
type Deity = any;
|
||||
|
||||
interface Props {
|
||||
currentDeity: string;
|
||||
deities: Record<string, Deity>;
|
||||
deitySystemState?: any;
|
||||
petSystemState?: any;
|
||||
dailyPrayerCount?: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
defineEmits(['switchDeity', 'addFavor']);
|
||||
const emit = defineEmits(['switchDeity', 'addFavor', 'onJiaobei', 'evolve']);
|
||||
|
||||
const activeTab = ref('PRAY');
|
||||
const isTossing = ref(false);
|
||||
|
|
@ -263,77 +402,213 @@ const JiaobeiResult = {
|
|||
};
|
||||
|
||||
const lotPhase = ref<string>(LotPhase.Idle);
|
||||
const drawnLotNumber = ref<number | null>(null);
|
||||
const drawnLot = ref<any>(null);
|
||||
const saintCupCount = ref(0);
|
||||
|
||||
const TAB_ACTIONS = [
|
||||
{ id: 'PRAY', label: 'PRAY (祈福)', icon: Sparkles },
|
||||
{ id: 'LOT', label: 'LOT (求籤)', icon: Scroll },
|
||||
{ id: 'VERIFY', label: 'VERIFY (驗證)', icon: CheckCircle2 },
|
||||
{ id: 'JIAOBEI', label: 'JIAOBEI (擲筊)', icon: Repeat },
|
||||
{ id: 'PRAY', label: '祈禱', icon: Sparkles },
|
||||
{ id: 'LOT', label: '求籤', icon: Scroll },
|
||||
{ id: 'JIAOBEI', label: '擲筊', icon: Repeat },
|
||||
];
|
||||
|
||||
const LOT_RESULT_DATA = {
|
||||
number: "第八十六籤",
|
||||
level: "上籤",
|
||||
poem: [
|
||||
"春來花發映陽臺",
|
||||
"萬里舟行進寶來",
|
||||
"躍過禹門三級浪",
|
||||
"恰如平地一聲雷"
|
||||
],
|
||||
meaning: "此卦上朝見帝之象。凡事太吉大利也。",
|
||||
interpretation: "朝帝受職。如貧得寶。謀望從心。卦中第一。此籤從心所欲。諸事皆吉。",
|
||||
storyTitle: "商絡中三元",
|
||||
story: "三元記。明朝。商絡。浙江人。父早亡。商絡三元及第。喻步步高升也。(三元即三級試。鄉試解元。省試會元。殿試狀元)"
|
||||
const activeDeity = computed(() => props.deities[props.currentDeity] || Object.values(props.deities)[0]);
|
||||
|
||||
// Computed for Stage Logic
|
||||
const activeDeityState = computed(() => {
|
||||
if (!props.deitySystemState?.collectedDeities) return null;
|
||||
return props.deitySystemState.collectedDeities.find((d: any) => d.id === props.currentDeity);
|
||||
});
|
||||
|
||||
const activeStage = computed(() => {
|
||||
if (!activeDeity.value || !activeDeityState.value) return null;
|
||||
return activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel);
|
||||
});
|
||||
|
||||
const nextStageExp = computed(() => {
|
||||
if (!activeDeity.value || !activeDeityState.value) return 100;
|
||||
const nextStage = activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel + 1);
|
||||
return nextStage ? nextStage.requiredExp : 9999;
|
||||
});
|
||||
|
||||
const isMaxLevel = computed(() => {
|
||||
if (!activeDeity.value || !activeDeityState.value) return false;
|
||||
return activeDeityState.value.stageLevel >= activeDeity.value.stages.length;
|
||||
});
|
||||
|
||||
const expPercentage = computed(() => {
|
||||
if (!activeDeityState.value) return 0;
|
||||
if (isMaxLevel.value) return 100;
|
||||
return Math.min(100, (activeDeityState.value.exp / nextStageExp.value) * 100);
|
||||
});
|
||||
|
||||
const activeDeityFavor = computed(() => {
|
||||
if (!props.petSystemState?.deityFavors) return 0;
|
||||
return props.petSystemState.deityFavors[props.currentDeity] || 0;
|
||||
});
|
||||
|
||||
const canPray = computed(() => {
|
||||
const prayCount = props.dailyPrayerCount || 0;
|
||||
return prayCount < 3 && activeDeityFavor.value < 100;
|
||||
});
|
||||
|
||||
const questProgress = (questId: string | number) => {
|
||||
return activeDeityState.value?.quests?.[questId] || 0;
|
||||
};
|
||||
|
||||
const activeDeity = computed(() => props.deities[props.currentDeity]);
|
||||
|
||||
const calculateToss = (): string => {
|
||||
const rand = Math.random();
|
||||
if (rand < 0.5) return JiaobeiResult.Saint;
|
||||
if (rand < 0.75) return JiaobeiResult.Smile;
|
||||
return JiaobeiResult.Cry;
|
||||
const getDeityStageLevel = (deityId: string) => {
|
||||
if (!props.deitySystemState?.collectedDeities) return 1;
|
||||
const deity = props.deitySystemState.collectedDeities.find((d: any) => d.id === deityId);
|
||||
return deity ? deity.stageLevel : 1;
|
||||
};
|
||||
|
||||
const handleToss = (isLotVerify = false) => {
|
||||
if (isTossing.value) return;
|
||||
isTossing.value = true;
|
||||
lastResult.value = null;
|
||||
const canEvolveDeity = computed(() => {
|
||||
if (!activeDeityState.value || !activeDeity.value) return false;
|
||||
if (isMaxLevel.value) return false;
|
||||
|
||||
setTimeout(() => {
|
||||
const result = calculateToss();
|
||||
isTossing.value = false;
|
||||
lastResult.value = result;
|
||||
// Check if all current stage quests are completed
|
||||
const quests = currentStageQuests.value;
|
||||
if (!quests || quests.length === 0) return false;
|
||||
|
||||
if (isLotVerify) {
|
||||
if (result === JiaobeiResult.Saint) {
|
||||
saintCupCount.value++;
|
||||
if (saintCupCount.value >= 3) {
|
||||
lotPhase.value = LotPhase.Success;
|
||||
}
|
||||
} else {
|
||||
lotPhase.value = LotPhase.Failed;
|
||||
}
|
||||
return quests.every((q: any) => questProgress(q.id) >= q.target);
|
||||
});
|
||||
|
||||
const handleEvolve = () => {
|
||||
if (canEvolveDeity.value) {
|
||||
emit('evolve');
|
||||
}
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const currentStageBuffs = computed(() => {
|
||||
return activeStage.value?.buffs || {};
|
||||
});
|
||||
|
||||
const currentStageQuests = computed(() => {
|
||||
if (!activeDeity.value || !activeDeityState.value) return [];
|
||||
// Get current stage quests (quests to complete this stage)
|
||||
const currentStage = activeDeity.value.stages.find((s: any) => s.level === activeDeityState.value.stageLevel);
|
||||
return currentStage?.quests || [];
|
||||
});
|
||||
|
||||
|
||||
|
||||
const formatBuffKey = (key: string | number): string => {
|
||||
const keyStr = String(key);
|
||||
const keyMap: Record<string, string> = {
|
||||
luck: '幸運',
|
||||
gameSuccessRate: '遊戲成功率',
|
||||
sicknessReduction: '抗病',
|
||||
evolutionSpeed: '進化速度',
|
||||
cleanlinessRetention: '清潔保持',
|
||||
moodRetention: '心情保持',
|
||||
trainingExp: '訓練經驗',
|
||||
workGold: '工作金幣',
|
||||
happinessRecovery: '快樂恢復',
|
||||
healthRecovery: '健康恢復',
|
||||
sicknessImmune: '免疫生病',
|
||||
dropRate: '掉落率',
|
||||
resourceGain: '資源獲得',
|
||||
intGain: '智力成長',
|
||||
miniGameBonus: '小遊戲獎勵',
|
||||
breedingSuccess: '繁殖成功率',
|
||||
badEventReduction: '壞事件減免'
|
||||
};
|
||||
return keyMap[keyStr] || keyStr;
|
||||
};
|
||||
|
||||
const formatBuffValue = (value: any): string => {
|
||||
if (typeof value === 'boolean') return value ? '是' : '否';
|
||||
if (typeof value === 'number') {
|
||||
if (value < 1) return `+${(value * 100).toFixed(0)}%`;
|
||||
return `+${value}`;
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const getJiaobeiResultText = (result: string): string => {
|
||||
switch (result) {
|
||||
case 'Saint': return '聖杯 (允杯)';
|
||||
case 'Smile': return '笑杯';
|
||||
case 'Cry': return '陰杯 (怒杯)';
|
||||
default: return result;
|
||||
}
|
||||
};
|
||||
|
||||
const formatLotType = (lotType: string): string => {
|
||||
const typeMap: Record<string, string> = {
|
||||
guanyin_100: '觀音一百籤',
|
||||
mazu_60: '媽祖六十甲子籤',
|
||||
moon_blocks: '擲筊',
|
||||
tarot_major: '塔羅大牌占卜'
|
||||
};
|
||||
return typeMap[lotType] || lotType;
|
||||
};
|
||||
|
||||
// Random dialogue - stable until deity changes
|
||||
const randomDialogue = ref('');
|
||||
watch(() => props.currentDeity, () => {
|
||||
if (!activeDeity.value?.dialogues || activeDeity.value.dialogues.length === 0) {
|
||||
randomDialogue.value = '';
|
||||
} else {
|
||||
const randomIndex = Math.floor(Math.random() * activeDeity.value.dialogues.length);
|
||||
randomDialogue.value = activeDeity.value.dialogues[randomIndex];
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const getFirstStory = (storyText: string) => {
|
||||
if (!storyText) return '';
|
||||
const parts = storyText.split(/2\./);
|
||||
return parts[0].trim();
|
||||
};
|
||||
|
||||
// --- Methods ---
|
||||
|
||||
const handleDrawLot = () => {
|
||||
lotPhase.value = LotPhase.Drawing;
|
||||
setTimeout(() => {
|
||||
drawnLotNumber.value = Math.floor(Math.random() * 60) + 1;
|
||||
lotPhase.value = LotPhase.PendingVerify;
|
||||
saintCupCount.value = 0;
|
||||
lotPhase.value = LotPhase.Drawing;
|
||||
setTimeout(() => {
|
||||
const randomIndex = Math.floor(Math.random() * guanyinLots.length);
|
||||
drawnLot.value = guanyinLots[randomIndex];
|
||||
lotPhase.value = LotPhase.PendingVerify;
|
||||
saintCupCount.value = 0;
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleToss = (isVerifying: boolean) => {
|
||||
if (isTossing.value) return;
|
||||
isTossing.value = true;
|
||||
lastResult.value = null;
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
const rand = Math.random();
|
||||
let result = '';
|
||||
if (rand < 0.5) result = JiaobeiResult.Saint;
|
||||
else if (rand < 0.75) result = JiaobeiResult.Smile;
|
||||
else result = JiaobeiResult.Cry;
|
||||
|
||||
lastResult.value = result;
|
||||
isTossing.value = false;
|
||||
|
||||
if (activeTab.value === 'JIAOBEI') {
|
||||
emit('onJiaobei', result);
|
||||
}
|
||||
|
||||
if (isVerifying) {
|
||||
if (result === JiaobeiResult.Saint) {
|
||||
saintCupCount.value++;
|
||||
if (saintCupCount.value >= 3) {
|
||||
lotPhase.value = LotPhase.Result;
|
||||
}
|
||||
} else {
|
||||
saintCupCount.value = 0;
|
||||
lotPhase.value = LotPhase.Failed;
|
||||
}
|
||||
}
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const resetLot = () => {
|
||||
lotPhase.value = LotPhase.Idle;
|
||||
drawnLotNumber.value = null;
|
||||
saintCupCount.value = 0;
|
||||
lastResult.value = null;
|
||||
lotPhase.value = LotPhase.Idle;
|
||||
drawnLot.value = null;
|
||||
saintCupCount.value = 0;
|
||||
lastResult.value = null;
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="flex flex-col h-full gap-2">
|
||||
<div class="flex flex-col h-full gap-2 overflow-visible">
|
||||
|
||||
<!-- 1. 已装备装备栏 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||
<div v-for="(slotConfig, key) in EQUIPMENT_SLOTS" :key="key" class="border border-[#4a3b5e] bg-[#0f0816] p-2 flex flex-col gap-2">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 overflow-visible">
|
||||
<div v-for="(slotConfig, key) in EQUIPMENT_SLOTS" :key="key" class="border border-[#4a3b5e] bg-[#0f0816] p-2 flex flex-col gap-2 overflow-visible">
|
||||
<!-- 装备槽标题 -->
|
||||
<div class="flex items-center gap-2 justify-center border-b border-[#2b193f] pb-1">
|
||||
<component :is="SLOT_ICONS[key]" :size="14" class="text-[#8f80a0]" />
|
||||
|
|
@ -11,42 +11,179 @@
|
|||
</div>
|
||||
|
||||
<!-- 实际装备槽 -->
|
||||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center">
|
||||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center relative">
|
||||
<span class="text-[9px] text-[#8f80a0] mb-1">装备</span>
|
||||
<div v-if="getEquippedItem(key, false)" class="flex flex-col items-center gap-1">
|
||||
<button
|
||||
v-if="getEquippedItem(key, false)"
|
||||
class="flex flex-col items-center gap-1 hover:bg-[#321e4a] transition-all p-1 rounded w-full"
|
||||
@mouseenter="onEquippedItemMouseEnter(`${key}-equipment`)"
|
||||
@mouseleave="onItemMouseLeave"
|
||||
>
|
||||
<div class="relative w-8 h-8">
|
||||
<PixelItemIcon :category="getEquippedItem(key, false)!.category" :color="ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color" />
|
||||
</div>
|
||||
<span class="text-xs text-center truncate w-full px-1 font-bold" :style="{ color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">{{ getEquippedItem(key, false)!.name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 懸浮卡片 -->
|
||||
<div
|
||||
v-show="hoveredEquippedSlot === `${key}-equipment`"
|
||||
@mouseenter="onTooltipMouseEnter"
|
||||
@mouseleave="onTooltipMouseLeave"
|
||||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||||
>
|
||||
<!-- 物品头部 -->
|
||||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||||
:style="{ borderColor: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">
|
||||
<PixelItemIcon
|
||||
:category="getEquippedItem(key, false)!.category"
|
||||
:color="ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-bold mb-1"
|
||||
:style="{ color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color }">
|
||||
{{ getEquippedItem(key, false)!.name }}
|
||||
</div>
|
||||
<div class="flex gap-2 text-[10px] flex-wrap">
|
||||
<span class="px-2 py-0.5 bg-[#1b1026] border border-[#4a3b5e] rounded text-[#8f80a0]">
|
||||
{{ getItemTypeName(getEquippedItem(key, false)!.type) }}
|
||||
</span>
|
||||
<span class="px-2 py-0.5 border rounded font-bold"
|
||||
:style="{
|
||||
borderColor: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color,
|
||||
color: ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.color
|
||||
}">
|
||||
{{ ITEM_RARITY[getEquippedItem(key, false)!.rarity]?.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 描述 -->
|
||||
<p class="text-[11px] text-[#e0d8f0] italic mb-3 leading-relaxed">
|
||||
"{{ getEquippedItem(key, false)!.description }}"
|
||||
</p>
|
||||
|
||||
<!-- 效果 -->
|
||||
<div v-if="getEquippedItem(key, false)!.effects" class="mb-3 space-y-1">
|
||||
<div class="text-[10px] text-[#99e550] font-bold mb-1">效果</div>
|
||||
<div v-if="getEquippedItem(key, false)!.effects.flat" class="space-y-1">
|
||||
<div v-for="(val, effectKey) in getEquippedItem(key, false)!.effects.flat" :key="effectKey"
|
||||
class="text-[10px] text-[#9fd75b] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||||
<span class="text-[#8f80a0]">{{ formatBuffKey(effectKey) }}</span>
|
||||
<span class="font-mono font-bold">+{{ val }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getEquippedItem(key, false)!.effects.percent" class="space-y-1 mt-1">
|
||||
<div v-for="(val, effectKey) in getEquippedItem(key, false)!.effects.percent" :key="effectKey"
|
||||
class="text-[10px] text-[#2ce8f4] flex justify-between border-b border-[#2b193f] border-dashed pb-1">
|
||||
<span class="text-[#8f80a0]">{{ formatBuffKey(effectKey) }}</span>
|
||||
<span class="font-mono font-bold">+{{ (val * 100).toFixed(0) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卸下按钮 -->
|
||||
<div class="space-y-2 pt-2 border-t border-[#4a3b5e]">
|
||||
<button
|
||||
@click.stop="$emit('unequip', key, false)"
|
||||
class="w-full px-3 py-2 bg-[#8f80a0] text-[#1b1026] rounded text-xs font-bold hover:bg-[#a598b8] transition-colors"
|
||||
>
|
||||
卸下裝備
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<span v-else class="text-[10px] text-[#4a3b5e] z-10 opacity-50">空</span>
|
||||
</div>
|
||||
|
||||
<!-- 外观槽 -->
|
||||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center">
|
||||
<div class="bg-[#150c1f] border border-[#4a3b5e] p-2 min-h-[40px] flex flex-col items-center justify-center relative">
|
||||
<span class="text-[9px] text-[#8f80a0] mb-1">外觀</span>
|
||||
<div v-if="getEquippedItem(key, true)" class="flex flex-col items-center gap-1">
|
||||
<button
|
||||
v-if="getEquippedItem(key, true)"
|
||||
class="flex flex-col items-center gap-1 hover:bg-[#321e4a] transition-all p-1 rounded w-full"
|
||||
@mouseenter="onEquippedItemMouseEnter(`${key}-appearance`)"
|
||||
@mouseleave="onItemMouseLeave"
|
||||
>
|
||||
<div class="relative w-8 h-8">
|
||||
<PixelItemIcon :category="getEquippedItem(key, true)!.category" :color="ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color" />
|
||||
</div>
|
||||
<span class="text-xs text-center truncate w-full px-1 font-bold" :style="{ color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">{{ getEquippedItem(key, true)!.name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 懸浮卡片 -->
|
||||
<div
|
||||
v-show="hoveredEquippedSlot === `${key}-appearance`"
|
||||
@mouseenter="onTooltipMouseEnter"
|
||||
@mouseleave="onTooltipMouseLeave"
|
||||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||||
>
|
||||
<!-- 物品头部 -->
|
||||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||||
:style="{ borderColor: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">
|
||||
<PixelItemIcon
|
||||
:category="getEquippedItem(key, true)!.category"
|
||||
:color="ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-bold mb-1"
|
||||
:style="{ color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color }">
|
||||
{{ getEquippedItem(key, true)!.name }}
|
||||
</div>
|
||||
<div class="flex gap-2 text-[10px] flex-wrap">
|
||||
<span class="px-2 py-0.5 bg-[#1b1026] border border-[#4a3b5e] rounded text-[#8f80a0]">
|
||||
{{ getItemTypeName(getEquippedItem(key, true)!.type) }}
|
||||
</span>
|
||||
<span class="px-2 py-0.5 border rounded font-bold"
|
||||
:style="{
|
||||
borderColor: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color,
|
||||
color: ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.color
|
||||
}">
|
||||
{{ ITEM_RARITY[getEquippedItem(key, true)!.rarity]?.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 描述 -->
|
||||
<p class="text-[11px] text-[#e0d8f0] italic mb-3 leading-relaxed">
|
||||
"{{ getEquippedItem(key, true)!.description }}"
|
||||
</p>
|
||||
|
||||
<!-- 卸下按钮 -->
|
||||
<div class="space-y-2 pt-2 border-t border-[#4a3b5e]">
|
||||
<button
|
||||
@click.stop="$emit('unequip', key, true)"
|
||||
class="w-full px-3 py-2 bg-[#d584fb] text-[#1b1026] rounded text-xs font-bold hover:bg-[#e5a4ff] transition-colors"
|
||||
>
|
||||
卸下外觀
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<span v-else class="text-[10px] text-[#4a3b5e] z-10 opacity-50">空</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 背包区域 - 全宽 + 固定高度 + 滚动 -->
|
||||
<div class="flex-grow overflow-hidden mt-2">
|
||||
<!-- 2. 背包区域 - 固定高度 + 滚动 -->
|
||||
<div class="flex-grow overflow-visible mt-2">
|
||||
<PixelFrame class="h-full flex flex-col bg-[#1b1026]" :title="`背包 (${items.filter(i => !i.isEquipped).length})`">
|
||||
<div class="overflow-y-auto p-3 custom-scrollbar" style="max-height: calc(100vh - 400px);">
|
||||
<div class="overflow-y-auto overflow-x-visible p-3 custom-scrollbar" style="height: 400px;">
|
||||
<div class="grid grid-cols-6 gap-2">
|
||||
<!-- 30个格子(6x5) -->
|
||||
<div v-for="index in 30" :key="index" class="relative aspect-square">
|
||||
<!-- 动态格子数量:基础30格,有道具时自动扩展 -->
|
||||
<div v-for="index in totalSlots" :key="index" class="relative aspect-square">
|
||||
|
||||
<!-- 有物品:显示物品 + 悬浮卡片 -->
|
||||
<template v-if="getInventorySlotItem(index - 1)">
|
||||
<button class="w-full h-full p-2 flex flex-col items-center justify-center gap-1 border-2 transition-all group rounded-sm border-[#4a3b5e] hover:border-[#8f80a0] hover:bg-[#321e4a] bg-[#2b193f]">
|
||||
<button
|
||||
class="w-full h-full p-2 flex flex-col items-center justify-center gap-1 border-2 transition-all group rounded-sm border-[#4a3b5e] hover:border-[#8f80a0] hover:bg-[#321e4a] bg-[#2b193f]"
|
||||
@mouseenter="onItemMouseEnter(index - 1)"
|
||||
@mouseleave="onItemMouseLeave"
|
||||
>
|
||||
<div class="relative w-10 h-10">
|
||||
<PixelItemIcon
|
||||
:category="getInventorySlotItem(index - 1).category"
|
||||
|
|
@ -77,7 +214,12 @@
|
|||
>{{ getInventorySlotItem(index - 1).name }}</span>
|
||||
|
||||
<!-- 详细悬浮卡片 -->
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity z-50 w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar">
|
||||
<div
|
||||
v-show="hoveredItemIndex === index - 1"
|
||||
@mouseenter="onTooltipMouseEnter"
|
||||
@mouseleave="onTooltipMouseLeave"
|
||||
class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-3 bg-[#0f0816] border-2 border-[#4a3b5e] rounded-sm shadow-xl pointer-events-auto z-[100] w-72 text-left max-h-[400px] overflow-y-auto custom-scrollbar"
|
||||
>
|
||||
<!-- 物品头部 -->
|
||||
<div class="flex gap-3 mb-3 pb-3 border-b border-[#4a3b5e]">
|
||||
<div class="w-16 h-16 bg-[#1b1026] border-2 rounded flex items-center justify-center p-2 flex-shrink-0"
|
||||
|
|
@ -233,13 +375,90 @@ const props = defineProps<Props>();
|
|||
defineEmits(['equip', 'unequip', 'use', 'delete', 'repair']);
|
||||
|
||||
const selectedItemId = ref<string | null>(null);
|
||||
const hoveredItemIndex = ref<number | null>(null);
|
||||
const hoveredEquippedSlot = ref<string | null>(null); // 新增:用於追蹤裝備槽的懸浮狀態
|
||||
const isHoveringTooltip = ref<boolean>(false);
|
||||
const hideTooltipTimeout = ref<number | null>(null);
|
||||
|
||||
const selectedItem = computed(() => props.items.find(i => i.id === selectedItemId.value));
|
||||
|
||||
// 计算总格子数量:基础30格,有道具时自动扩展
|
||||
const totalSlots = computed(() => {
|
||||
const unequippedItems = props.items.filter(i => !i.isEquipped);
|
||||
const minSlots = 30;
|
||||
const itemCount = unequippedItems.length;
|
||||
// 如果道具數量超過30,則自動擴展到下一個6的倍數
|
||||
if (itemCount > minSlots) {
|
||||
return Math.ceil(itemCount / 6) * 6;
|
||||
}
|
||||
return minSlots;
|
||||
});
|
||||
|
||||
const setSelectedItemId = (id: string | undefined) => {
|
||||
if (id) selectedItemId.value = id;
|
||||
};
|
||||
|
||||
// 處理裝備槽的滑鼠進入事件
|
||||
const onEquippedItemMouseEnter = (slotKey: string) => {
|
||||
// 清除任何待處理的隱藏 timeout
|
||||
if (hideTooltipTimeout.value) {
|
||||
clearTimeout(hideTooltipTimeout.value);
|
||||
hideTooltipTimeout.value = null;
|
||||
}
|
||||
// 設置當前懸停的裝備槽
|
||||
hoveredEquippedSlot.value = slotKey;
|
||||
// 清除背包道具的懸停狀態
|
||||
hoveredItemIndex.value = null;
|
||||
};
|
||||
|
||||
// 處理背包道具的滑鼠進入事件
|
||||
const onItemMouseEnter = (itemIndex: number) => {
|
||||
// 清除任何待處理的隱藏 timeout
|
||||
if (hideTooltipTimeout.value) {
|
||||
clearTimeout(hideTooltipTimeout.value);
|
||||
hideTooltipTimeout.value = null;
|
||||
}
|
||||
// 設置當前懸停的道具索引
|
||||
hoveredItemIndex.value = itemIndex;
|
||||
// 清除裝備槽的懸停狀態
|
||||
hoveredEquippedSlot.value = null;
|
||||
};
|
||||
|
||||
const onItemMouseLeave = () => {
|
||||
// 清除之前的timeout(如果有)
|
||||
if (hideTooltipTimeout.value) {
|
||||
clearTimeout(hideTooltipTimeout.value);
|
||||
}
|
||||
|
||||
// 延遲較長時間再檢查,給使用者足夠時間移動到tooltip上
|
||||
hideTooltipTimeout.value = setTimeout(() => {
|
||||
if (!isHoveringTooltip.value) {
|
||||
hoveredItemIndex.value = null;
|
||||
hoveredEquippedSlot.value = null;
|
||||
}
|
||||
}, 300); // 增加到 300ms
|
||||
};
|
||||
|
||||
const onTooltipMouseEnter = () => {
|
||||
// 取消隱藏的timeout
|
||||
if (hideTooltipTimeout.value) {
|
||||
clearTimeout(hideTooltipTimeout.value);
|
||||
hideTooltipTimeout.value = null;
|
||||
}
|
||||
isHoveringTooltip.value = true;
|
||||
};
|
||||
|
||||
const onTooltipMouseLeave = () => {
|
||||
// 滑鼠離開 tooltip,立即隱藏
|
||||
isHoveringTooltip.value = false;
|
||||
hoveredItemIndex.value = null;
|
||||
hoveredEquippedSlot.value = null;
|
||||
if (hideTooltipTimeout.value) {
|
||||
clearTimeout(hideTooltipTimeout.value);
|
||||
hideTooltipTimeout.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Map data constants to local helpers for template compatibility
|
||||
const ItemType = {
|
||||
Equipment: ITEM_TYPE.EQUIPMENT,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
<template>
|
||||
<div class="w-full h-full relative image-pixelated flex items-center justify-center" :class="{ 'grayscale': isDead }">
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" shapeRendering="crispEdges" class="w-full h-full">
|
||||
<!-- DEITY STAGE - Use PNG Images -->
|
||||
<img
|
||||
v-if="deityId"
|
||||
:src="deityImagePath"
|
||||
:alt="deityId"
|
||||
class="image-pixelated"
|
||||
style="image-rendering: pixelated; image-rendering: crisp-edges; max-width: 80%; max-height: 80%; object-fit: contain;"
|
||||
/>
|
||||
|
||||
<!-- PET/EGG SVG -->
|
||||
<svg v-else viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" shapeRendering="crispEdges" class="w-full h-full">
|
||||
<!-- EGG STAGE -->
|
||||
<g v-if="isEgg">
|
||||
<!-- Egg Base -->
|
||||
|
|
@ -60,56 +70,6 @@
|
|||
</g>
|
||||
</g>
|
||||
|
||||
<!-- DEITY STAGE -->
|
||||
<g v-else-if="deityId">
|
||||
<!-- Mazu (妈祖) -->
|
||||
<g v-if="deityId === 'mazu'">
|
||||
<rect x="10" y="4" width="12" height="12" fill="#ffe0bd" /> <!-- Face -->
|
||||
<rect x="8" y="4" width="2" height="12" fill="#1a1a1a" /> <!-- Hair L -->
|
||||
<rect x="22" y="4" width="2" height="12" fill="#1a1a1a" /> <!-- Hair R -->
|
||||
<rect x="8" y="2" width="16" height="4" fill="#d4af37" /> <!-- Crown Base -->
|
||||
<rect x="14" y="0" width="4" height="2" fill="#d4af37" /> <!-- Crown Top -->
|
||||
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<path d="M8 16 H24 V28 H8 Z" fill="#ffa500" /> <!-- Robe -->
|
||||
<rect x="14" y="16" width="4" height="12" fill="#d4af37" opacity="0.5" /> <!-- Robe Detail -->
|
||||
</g>
|
||||
|
||||
<!-- Earth God (土地公) -->
|
||||
<g v-else-if="deityId === 'earth_god'">
|
||||
<rect x="10" y="6" width="12" height="10" fill="#f0c0a8" /> <!-- Face -->
|
||||
<rect x="8" y="4" width="16" height="4" fill="#1a1a1a" /> <!-- Hat Base -->
|
||||
<rect x="10" y="2" width="12" height="2" fill="#1a1a1a" /> <!-- Hat Top -->
|
||||
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="10" y="14" width="12" height="6" fill="#f0f0f0" /> <!-- Beard -->
|
||||
<path d="M8 16 H24 V28 H8 Z" fill="#d75b5b" /> <!-- Robe -->
|
||||
<rect x="14" y="20" width="4" height="2" fill="#8e5c2e" /> <!-- Belt -->
|
||||
</g>
|
||||
|
||||
<!-- Matchmaker (月老) -->
|
||||
<g v-else-if="deityId === 'matchmaker'">
|
||||
<rect x="10" y="6" width="12" height="10" fill="#ffe0bd" /> <!-- Face -->
|
||||
<rect x="10" y="4" width="12" height="4" fill="#f0f0f0" /> <!-- Hair -->
|
||||
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="10" y="14" width="12" height="4" fill="#f0f0f0" /> <!-- Beard -->
|
||||
<path d="M8 16 H24 V28 H8 Z" fill="#d95763" /> <!-- Robe -->
|
||||
<rect x="20" y="18" width="4" height="4" fill="#ff0000" /> <!-- Red Thread Ball -->
|
||||
</g>
|
||||
|
||||
<!-- Wenchang (文昌) -->
|
||||
<g v-else>
|
||||
<rect x="10" y="6" width="12" height="10" fill="#ffe0bd" /> <!-- Face -->
|
||||
<rect x="8" y="2" width="16" height="6" fill="#1a1a1a" /> <!-- Hat -->
|
||||
<rect x="12" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="18" y="10" width="2" height="2" fill="#000" opacity="0.6" /> <!-- Eye -->
|
||||
<rect x="12" y="14" width="8" height="2" fill="#1a1a1a" /> <!-- Mustache -->
|
||||
<path d="M8 16 H24 V28 H8 Z" fill="#9fd75b" /> <!-- Robe -->
|
||||
<rect x="20" y="18" width="2" height="8" fill="#8d6e63" /> <!-- Brush -->
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- PORTRAIT STAGE (Baby, Child, Adult) -->
|
||||
<g v-else>
|
||||
<!-- Background Glow (Optional) -->
|
||||
|
|
@ -254,6 +214,7 @@ interface Props {
|
|||
skinColor?: string; // Fallback
|
||||
outfitColor?: string; // Fallback
|
||||
deityId?: string;
|
||||
stageLevel?: number;
|
||||
isDead?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +224,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
stats: () => ({ str: 0, int: 0, dex: 0, happiness: 50, generation: 1, age: 0 }),
|
||||
skinColor: '#ffdbac',
|
||||
outfitColor: '#78909c',
|
||||
stageLevel: 1,
|
||||
isDead: false
|
||||
});
|
||||
|
||||
|
|
@ -331,6 +293,12 @@ const outfitColor = computed(() => {
|
|||
return props.outfitColor;
|
||||
});
|
||||
|
||||
const deityImagePath = computed(() => {
|
||||
if (!props.deityId) return '';
|
||||
const stage = props.stageLevel || 1;
|
||||
return `/assets/deities/${props.deityId}-stage${stage}.png`;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -442,6 +442,24 @@
|
|||
<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>
|
||||
|
||||
<!-- Deity Button -->
|
||||
<button
|
||||
@click="$emit('openDeity')"
|
||||
class="w-10 bg-[#1b1026] border border-[#f6b26b] 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 text-[#f6b26b] group-hover:text-[#ffe762]">
|
||||
<Sparkles :size="16" />
|
||||
</div>
|
||||
|
||||
<!-- Corner Accents -->
|
||||
<div class="absolute top-0 left-0 w-1 h-1 border-t border-l border-[#f6b26b] opacity-50"></div>
|
||||
<div class="absolute top-0 right-0 w-1 h-1 border-t border-r border-[#f6b26b] opacity-50"></div>
|
||||
<div class="absolute bottom-0 left-0 w-1 h-1 border-b border-l border-[#f6b26b] opacity-50"></div>
|
||||
<div class="absolute bottom-0 right-0 w-1 h-1 border-b border-r border-[#f6b26b] opacity-50"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -463,7 +481,7 @@ interface Props {
|
|||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
defineEmits(['openAchievements', 'deletePet', 'openInventory']);
|
||||
defineEmits(['openAchievements', 'deletePet', 'openInventory', 'openDeity']);
|
||||
|
||||
// Determine mood based on happiness
|
||||
const mood = computed(() => {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
@openAchievements="showAchievements = true"
|
||||
@deletePet="handleDeletePet"
|
||||
@openInventory="showInventory = true"
|
||||
@openDeity="showGodSystem = true"
|
||||
/>
|
||||
<div v-else class="flex items-center justify-center h-32 md:h-full text-[#8f80a0] text-xs">
|
||||
Initializing...
|
||||
|
|
@ -88,7 +89,6 @@
|
|||
/>
|
||||
</PixelModal>
|
||||
|
||||
<!-- God System Overlay -->
|
||||
<PixelModal
|
||||
:isOpen="showGodSystem"
|
||||
@close="showGodSystem = false"
|
||||
|
|
@ -97,8 +97,13 @@
|
|||
<GodSystemOverlay
|
||||
:currentDeity="currentDeity"
|
||||
:deities="deities"
|
||||
:deitySystemState="petSystem?.deitySystem?.state"
|
||||
:petSystemState="systemState"
|
||||
:dailyPrayerCount="systemState?.dailyPrayerCount || 0"
|
||||
@switchDeity="handleSwitchDeity"
|
||||
@addFavor="handleAddFavor"
|
||||
@onJiaobei="handleJiaobei"
|
||||
@evolve="handleDeityEvolve"
|
||||
/>
|
||||
</PixelModal>
|
||||
|
||||
|
|
@ -171,6 +176,7 @@ import InventoryOverlay from '~/components/pixel/InventoryOverlay.vue';
|
|||
import GodSystemOverlay from '~/components/pixel/GodSystemOverlay.vue';
|
||||
import ShopOverlay from '~/components/pixel/ShopOverlay.vue';
|
||||
import AdventureOverlay from '~/components/pixel/AdventureOverlay.vue';
|
||||
import NamingOverlay from '~/components/pixel/NamingOverlay.vue';
|
||||
|
||||
import { PetSystem } from '../../core/pet-system.js';
|
||||
import { TempleSystem } from '../../core/temple-system.js';
|
||||
|
|
@ -185,7 +191,6 @@ import { ACHIEVEMENTS } from '../../data/achievements.js';
|
|||
|
||||
// Type definitions (minimal, based on actual data structures)
|
||||
type EntityStats = any;
|
||||
import NamingOverlay from '~/components/pixel/NamingOverlay.vue';
|
||||
type Deity = typeof DEITIES[0];
|
||||
type Item = typeof ITEMS[keyof typeof ITEMS];
|
||||
type AdventureLocation = typeof ADVENTURES[0];
|
||||
|
|
@ -217,7 +222,7 @@ if (typeof window !== 'undefined') {
|
|||
|
||||
// Reactive state mapped from PetSystem
|
||||
const systemState = ref<any>(null);
|
||||
const allDeities = ref<Deity[]>([]);
|
||||
// Removed: now using DEITIES directly for hot reload support
|
||||
const achievementsState = ref<any[]>([]);
|
||||
|
||||
const playerStats = computed<EntityStats>(() => {
|
||||
|
|
@ -230,7 +235,7 @@ const playerStats = computed<EntityStats>(() => {
|
|||
};
|
||||
|
||||
const s = systemState.value;
|
||||
const currentDeity = allDeities.value.find(d => d.id === s.currentDeityId);
|
||||
const currentDeity = DEITIES.find(d => d.id === s.currentDeityId);
|
||||
|
||||
// Calculate real-time age (stored ageSeconds + time since last tick)
|
||||
const timeSinceLastTick = s.lastTickTime ? (Date.now() - s.lastTickTime) / 1000 : 0;
|
||||
|
|
@ -292,7 +297,7 @@ const inventory = computed<Item[]>(() => {
|
|||
|
||||
const deities = computed(() => {
|
||||
const map: Record<string, Deity> = {};
|
||||
allDeities.value.forEach(d => {
|
||||
DEITIES.forEach(d => {
|
||||
const favor = systemState.value?.deityFavors?.[d.id] || 0;
|
||||
map[d.id] = { ...d, favor, maxFavor: 100 };
|
||||
});
|
||||
|
|
@ -349,7 +354,7 @@ const stateSyncInterval = ref<any>(null);
|
|||
onMounted(async () => {
|
||||
// Initialize Systems
|
||||
petSystem.value = new PetSystem(apiService);
|
||||
templeSystem.value = new TempleSystem(apiService, petSystem.value);
|
||||
templeSystem.value = new TempleSystem(petSystem.value, apiService);
|
||||
achievementSystem.value = new AchievementSystem(petSystem.value, null, templeSystem.value, apiService);
|
||||
|
||||
// Connect achievementSystem to petSystem (circular reference after creation)
|
||||
|
|
@ -370,8 +375,7 @@ onMounted(async () => {
|
|||
showNamingOverlay.value = true;
|
||||
}
|
||||
|
||||
// Load Deities
|
||||
allDeities.value = templeSystem.value.getDeities();
|
||||
// Deities are now loaded directly from DEITIES import for hot reload support
|
||||
|
||||
// Start Game Loop with callback to update UI
|
||||
petSystem.value.startTickLoop((newState: any) => {
|
||||
|
|
@ -730,6 +734,26 @@ const handleAddFavor = async (amount: number) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleJiaobei = async (payload: { result: string }) => {
|
||||
if (payload.result === 'saint' && petSystem.value?.deitySystem) {
|
||||
// Update quest progress for jiaobei type
|
||||
await petSystem.value.deitySystem.updateQuestProgress('jiaobei', 1);
|
||||
systemState.value = petSystem.value.getState();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeityEvolve = async () => {
|
||||
if (petSystem.value?.deitySystem) {
|
||||
const success = petSystem.value.deitySystem.evolve();
|
||||
if (success) {
|
||||
systemState.value = petSystem.value.getState();
|
||||
console.log('神明進化成功!');
|
||||
} else {
|
||||
console.log('進化失敗:條件不足');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleBuyItem = async (item: Item) => {
|
||||
const price = (item as any).price || 100;
|
||||
if (petSystem.value && systemState.value.coins >= price) {
|
||||
|
|
@ -828,6 +852,4 @@ const SHOP_ITEMS = Object.values(ITEMS).filter((item: any) => item.type === 'con
|
|||
...item,
|
||||
statsDescription: item.description
|
||||
}));
|
||||
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
import { DEITIES } from '../data/deities.js'
|
||||
import { TAROT_MAJOR_ARCANA } from '../data/tarot.js'
|
||||
import { QUEST_TYPES, checkQuestProgress } from '../data/deity-quests.js'
|
||||
|
||||
export class DeitySystem {
|
||||
constructor(petSystem) {
|
||||
this.petSystem = petSystem
|
||||
this.state = {
|
||||
collectedDeities: [], // Array of { id, stageLevel, exp, quests: { questId: progress } }
|
||||
currentDeityId: null,
|
||||
lastEncounterTime: 0,
|
||||
dailyFortune: null // { type: 'jiaobei'|'stick'|'tarot', result: ... }
|
||||
}
|
||||
}
|
||||
|
||||
// 保存狀態到 PetSystem (進而保存到 API/LocalStorage)
|
||||
async saveState() {
|
||||
if (this.petSystem && this.petSystem.updateState) {
|
||||
await this.petSystem.updateState({ deitySystemState: this.state })
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
async initialize(savedState) {
|
||||
if (savedState) {
|
||||
this.state = { ...this.state, ...savedState }
|
||||
}
|
||||
|
||||
// 確保至少有一個默認神明 (媽祖)
|
||||
if (this.state.collectedDeities.length === 0) {
|
||||
this.collectDeity('mazu');
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取當前神明資料
|
||||
getCurrentDeity() {
|
||||
if (!this.state.currentDeityId) return null
|
||||
return DEITIES.find(d => d.id === this.state.currentDeityId)
|
||||
}
|
||||
|
||||
// 獲取當前神明的狀態 (等級、經驗等)
|
||||
getCurrentDeityState() {
|
||||
if (!this.state.currentDeityId) return null
|
||||
return this.state.collectedDeities.find(d => d.id === this.state.currentDeityId)
|
||||
}
|
||||
|
||||
// 獲取當前階段詳細資料
|
||||
getCurrentStage() {
|
||||
const deity = this.getCurrentDeity()
|
||||
const deityState = this.getCurrentDeityState()
|
||||
if (!deity || !deityState) return null
|
||||
|
||||
return deity.stages.find(s => s.level === deityState.stageLevel)
|
||||
}
|
||||
|
||||
// 遭遇神明 (隨機)
|
||||
triggerEncounter() {
|
||||
const now = Date.now()
|
||||
// 簡單的冷卻檢查 (例如 1 小時)
|
||||
if (now - this.state.lastEncounterTime < 3600000) return null
|
||||
|
||||
// 隨機選擇一個未收集的神明
|
||||
const collectedIds = this.state.collectedDeities.map(d => d.id)
|
||||
const availableDeities = DEITIES.filter(d => !collectedIds.includes(d.id))
|
||||
|
||||
if (availableDeities.length === 0) return null
|
||||
|
||||
const randomDeity = availableDeities[Math.floor(Math.random() * availableDeities.length)]
|
||||
this.state.lastEncounterTime = now
|
||||
|
||||
return randomDeity
|
||||
}
|
||||
|
||||
// 嘗試邀請神明 (擲筊挑戰)
|
||||
// 這裡只負責邏輯,UI 負責顯示動畫
|
||||
// return: { success: boolean, cups: [boolean, boolean, boolean] } (true=聖杯)
|
||||
challengeInvite() {
|
||||
// 模擬擲筊 3 次
|
||||
const results = []
|
||||
let successCount = 0
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// 聖杯機率 50% (一正一反)
|
||||
// 這裡簡化邏輯:0=笑杯/陰杯, 1=聖杯
|
||||
const isHolyCup = Math.random() > 0.5
|
||||
results.push(isHolyCup)
|
||||
if (isHolyCup) successCount++
|
||||
}
|
||||
|
||||
// 連續 3 聖杯才算成功 (或者累積? 需求說是"三聖杯之後")
|
||||
// 通常"博杯"請神需要連續三聖杯
|
||||
const isSuccess = successCount === 3
|
||||
|
||||
return { success: isSuccess, results }
|
||||
}
|
||||
|
||||
// 收集神明
|
||||
collectDeity(deityId) {
|
||||
if (this.state.collectedDeities.some(d => d.id === deityId)) return false
|
||||
|
||||
const deityConfig = DEITIES.find(d => d.id === deityId)
|
||||
if (!deityConfig) return false
|
||||
|
||||
// 初始化神明狀態
|
||||
const newDeityState = {
|
||||
id: deityId,
|
||||
stageLevel: 1,
|
||||
exp: 0,
|
||||
quests: {} // 任務進度
|
||||
}
|
||||
|
||||
// 初始化任務進度 (第一階段的任務)
|
||||
const firstStage = deityConfig.stages[0];
|
||||
if (firstStage?.quests) {
|
||||
firstStage.quests.forEach(q => {
|
||||
newDeityState.quests[q.id] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
this.state.collectedDeities.push(newDeityState)
|
||||
|
||||
// 如果是第一個神明,自動設為當前
|
||||
if (!this.state.currentDeityId) {
|
||||
this.state.currentDeityId = deityId
|
||||
}
|
||||
|
||||
this.saveState();
|
||||
return true
|
||||
}
|
||||
|
||||
// 切換當前供奉神明
|
||||
switchDeity(deityId) {
|
||||
if (!this.state.collectedDeities.some(d => d.id === deityId)) return false
|
||||
this.state.currentDeityId = deityId
|
||||
this.saveState();
|
||||
return true
|
||||
}
|
||||
|
||||
// 更新任務進度
|
||||
updateQuestProgress(actionType, value = 1) {
|
||||
const deityState = this.getCurrentDeityState();
|
||||
const deityConfig = this.getCurrentDeity();
|
||||
|
||||
if (!deityState || !deityConfig) return [];
|
||||
|
||||
// 獲取當前階段的任務(修正:從下一階段改為當前階段)
|
||||
const currentStage = deityConfig.stages.find(s => s.level === deityState.stageLevel);
|
||||
if (!currentStage || !currentStage.quests) return [];
|
||||
|
||||
const updates = [];
|
||||
|
||||
currentStage.quests.forEach(quest => {
|
||||
// 檢查是否已經完成
|
||||
if (deityState.quests[quest.id] >= quest.target) return;
|
||||
|
||||
const oldProgress = deityState.quests[quest.id] || 0;
|
||||
const newProgress = checkQuestProgress(oldProgress, quest.target, actionType, quest.type, value);
|
||||
|
||||
if (newProgress !== oldProgress) {
|
||||
deityState.quests[quest.id] = newProgress;
|
||||
updates.push({
|
||||
questId: quest.id,
|
||||
progress: newProgress,
|
||||
target: quest.target,
|
||||
completed: newProgress >= quest.target
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (updates.length > 0) {
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
// 檢查是否可以進化
|
||||
canEvolve() {
|
||||
const deityState = this.getCurrentDeityState();
|
||||
const deityConfig = this.getCurrentDeity();
|
||||
if (!deityState || !deityConfig) return false;
|
||||
|
||||
// 檢查是否已達最高級
|
||||
if (deityState.stageLevel >= deityConfig.stages.length) return false;
|
||||
|
||||
// 檢查下一階需求(EXP)
|
||||
const nextStage = deityConfig.stages.find(s => s.level === deityState.stageLevel + 1);
|
||||
if (!nextStage) return false;
|
||||
|
||||
// 檢查經驗值 (如果有)
|
||||
if (deityState.exp < nextStage.requiredExp) return false;
|
||||
|
||||
// 檢查當前階段的所有任務是否完成(修正:檢查當前階段而非下一階段)
|
||||
const currentStage = deityConfig.stages.find(s => s.level === deityState.stageLevel);
|
||||
if (!currentStage || !currentStage.quests || currentStage.quests.length === 0) return true;
|
||||
|
||||
const allQuestsCompleted = currentStage.quests.every(q => {
|
||||
return (deityState.quests[q.id] || 0) >= q.target;
|
||||
});
|
||||
|
||||
return allQuestsCompleted;
|
||||
}
|
||||
|
||||
// 進化
|
||||
evolve() {
|
||||
if (!this.canEvolve()) return false;
|
||||
|
||||
const deityState = this.getCurrentDeityState();
|
||||
const deityConfig = this.getCurrentDeity();
|
||||
|
||||
deityState.stageLevel += 1;
|
||||
|
||||
// 初始化當前(剛進化到的)階段的任務
|
||||
const currentStage = deityConfig.stages.find(s => s.level === deityState.stageLevel);
|
||||
if (currentStage?.quests) {
|
||||
currentStage.quests.forEach(q => {
|
||||
if (!deityState.quests[q.id]) {
|
||||
deityState.quests[q.id] = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.saveState();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 塔羅占卜
|
||||
drawTarot() {
|
||||
const cardIndex = Math.floor(Math.random() * TAROT_MAJOR_ARCANA.length)
|
||||
const card = TAROT_MAJOR_ARCANA[cardIndex]
|
||||
|
||||
// 記錄今日運勢
|
||||
this.state.dailyFortune = {
|
||||
type: 'tarot',
|
||||
result: card,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
this.saveState();
|
||||
return card
|
||||
}
|
||||
|
||||
// 獲取當前生效的 Buffs
|
||||
getActiveBuffs() {
|
||||
const stage = this.getCurrentStage()
|
||||
if (!stage) return {}
|
||||
|
||||
// 如果是最終階段,確保包含所有強力 Buff
|
||||
// 邏輯上 Stage 3 的 buffs 已經包含了所有需要的屬性
|
||||
return stage.buffs || {}
|
||||
}
|
||||
|
||||
// 獲取狀態以供保存
|
||||
getState() {
|
||||
return this.state
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,15 @@ import { apiService } from './api-service.js'
|
|||
import { PET_SPECIES } from '../data/pet-species.js'
|
||||
import { FATES } from '../data/fates.js'
|
||||
import { DEITIES } from '../data/deities.js'
|
||||
import { DeitySystem } from './deity-system.js'
|
||||
import { QUEST_TYPES } from '../data/deity-quests.js'
|
||||
|
||||
export class PetSystem {
|
||||
constructor(api = apiService, achievementSystem = null, inventorySystem = null) {
|
||||
this.api = api
|
||||
this.achievementSystem = achievementSystem
|
||||
this.inventorySystem = inventorySystem
|
||||
this.deitySystem = new DeitySystem(this) // Initialize DeitySystem with PetSystem instance
|
||||
this.state = null
|
||||
this.speciesConfig = null
|
||||
this.tickInterval = null
|
||||
|
|
@ -48,6 +51,9 @@ export class PetSystem {
|
|||
this.calculateCombatStats()
|
||||
}
|
||||
|
||||
// 初始化神明系統
|
||||
await this.deitySystem.initialize(this.state.deitySystemState)
|
||||
|
||||
return this.state
|
||||
} catch (error) {
|
||||
console.error('[PetSystem] 初始化失敗:', error)
|
||||
|
|
@ -138,6 +144,15 @@ export class PetSystem {
|
|||
}
|
||||
}
|
||||
|
||||
// 2. 神明系統加成 (取代舊的神明加成邏輯)
|
||||
if (this.deitySystem) {
|
||||
const deityBuffs = this.deitySystem.getActiveBuffs()
|
||||
for (const [key, value] of Object.entries(deityBuffs)) {
|
||||
bonuses[key] = (bonuses[key] || 0) + value
|
||||
}
|
||||
}
|
||||
|
||||
/* 舊邏輯已廢棄,由 DeitySystem 接管
|
||||
// 2. 神明基礎加成
|
||||
if (this.state.currentDeityId) {
|
||||
const deity = DEITIES.find(d => d.id === this.state.currentDeityId)
|
||||
|
|
@ -169,6 +184,7 @@ export class PetSystem {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 5. 成就加成
|
||||
if (this.state.achievementBuffs) {
|
||||
|
|
@ -322,6 +338,14 @@ export class PetSystem {
|
|||
state.effectiveDex = effectiveDex
|
||||
state.effectiveLuck = effectiveLuck
|
||||
|
||||
// [DEBUG] 輸出屬性計算詳情
|
||||
console.groupCollapsed('[PetSystem] 屬性計算詳情');
|
||||
console.log('原始屬性:', { STR: state.str, INT: state.int, DEX: state.dex, LUCK: state.luck });
|
||||
console.log('加成來源:', bonuses);
|
||||
console.log('有效屬性 (原始+加成):', { STR: effectiveStr, INT: effectiveInt, DEX: effectiveDex, LUCK: effectiveLuck });
|
||||
console.log('戰鬥屬性:', { ATK: state.attack, DEF: state.defense, SPD: state.speed });
|
||||
console.groupEnd();
|
||||
|
||||
// 根據當前階段設定身高(每個階段固定)
|
||||
const currentStageConfig = this.speciesConfig.lifecycle.find(s => s.stage === state.stage)
|
||||
if (currentStageConfig && currentStageConfig.height) {
|
||||
|
|
@ -812,6 +836,11 @@ export class PetSystem {
|
|||
|
||||
this.calculateCombatStats()
|
||||
|
||||
// 觸發任務更新
|
||||
if (this.deitySystem) {
|
||||
this.deitySystem.updateQuestProgress(QUEST_TYPES.FEED, 1)
|
||||
}
|
||||
|
||||
return { success: true, hunger: newHunger, weight: newWeight, strGain, weightChange }
|
||||
}
|
||||
|
||||
|
|
@ -903,6 +932,12 @@ export class PetSystem {
|
|||
|
||||
this.calculateCombatStats()
|
||||
|
||||
// 觸發任務更新
|
||||
if (this.deitySystem) {
|
||||
this.deitySystem.updateQuestProgress(QUEST_TYPES.PLAY, 1)
|
||||
this.deitySystem.updateQuestProgress(QUEST_TYPES.MINIGAME_WIN, 1) // 假設玩耍算作小遊戲勝利,或者需要區分
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
happiness: newHappiness,
|
||||
|
|
@ -935,6 +970,11 @@ export class PetSystem {
|
|||
this.achievementSystem.checkPerfectState()
|
||||
}
|
||||
|
||||
// 觸發任務更新
|
||||
if (this.deitySystem) {
|
||||
this.deitySystem.updateQuestProgress(QUEST_TYPES.CLEAN, 1)
|
||||
}
|
||||
|
||||
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
|
||||
}
|
||||
|
||||
|
|
|
|||
233
data/deities.js
233
data/deities.js
|
|
@ -3,31 +3,72 @@ export const DEITIES = [
|
|||
{
|
||||
id: 'mazu',
|
||||
name: '媽祖',
|
||||
origin: 'eastern',
|
||||
personality: '溫柔守護',
|
||||
buffs: {
|
||||
gameSuccessRate: 0.1,
|
||||
sicknessReduction: 0.15,
|
||||
happinessRecovery: 0.25
|
||||
},
|
||||
buffDescriptions: ['小遊戲 +10%', '生病機率 -15%', '快樂恢復 +25%'],
|
||||
|
||||
// 好感度等級加成(每10點好感度)
|
||||
favorLevelBuffs: {
|
||||
interval: 10, // 每10點好感度提升一級
|
||||
buffsPerLevel: {
|
||||
str: 0.5, // 每級 +0.5 力量
|
||||
health: 1 // 每級 +1 最大健康
|
||||
// 進化階段
|
||||
stages: [
|
||||
{
|
||||
level: 1,
|
||||
name: '林默娘',
|
||||
title: '通靈少女',
|
||||
description: '福建沿海的漁村少女,天資聰穎,識天文地理。',
|
||||
icon: 'deity-mazu-1',
|
||||
requiredExp: 0,
|
||||
buffs: {
|
||||
sicknessReduction: 0.05,
|
||||
happinessRecovery: 0.1
|
||||
},
|
||||
quests: [
|
||||
{ id: 'mazu_s1_q1', type: 'jiaobei', target: 1, description: '累積獲得 1 次聖杯' },
|
||||
]
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
name: '媽祖',
|
||||
title: '海神',
|
||||
description: '守護海上平安的女神,深受漁民愛戴。',
|
||||
icon: 'deity-mazu',
|
||||
requiredExp: 0,
|
||||
buffs: {
|
||||
gameSuccessRate: 0.1,
|
||||
sicknessReduction: 0.15,
|
||||
happinessRecovery: 0.25
|
||||
},
|
||||
quests: [
|
||||
{ id: 'mazu_s2_q1', type: 'jiaobei', target: 10, description: '累積獲得 10 次聖杯' },
|
||||
{ id: 'mazu_s2_q2', type: 'clean', target: 20, description: '清理 20 次便便(保持環境整潔)' }
|
||||
]
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
name: '天上聖母',
|
||||
title: '天后',
|
||||
description: '受歷代皇帝敕封的最高女神,神威顯赫。',
|
||||
icon: 'deity-mazu-3',
|
||||
requiredExp: 0,
|
||||
buffs: {
|
||||
gameSuccessRate: 0.2,
|
||||
sicknessReduction: 0.3,
|
||||
happinessRecovery: 0.5,
|
||||
sicknessImmune: true,
|
||||
healthRecovery: 0.5
|
||||
},
|
||||
quests: [
|
||||
{ id: 'mazu_s3_q1', type: 'jiaobei', target: 50, description: '累積獲得 50 次聖杯' },
|
||||
{ id: 'mazu_s3_q2', type: 'heal', target: 10, description: '治療寵物 10 次' },
|
||||
{ id: 'mazu_s3_q3', type: 'play', target: 30, description: '陪伴玩耍 30 次' }
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
// 滿級特殊 Buff(好感度 = 100)
|
||||
maxFavorBuff: {
|
||||
id: 'mazu_divine_protection',
|
||||
name: '媽祖神佑',
|
||||
description: '媽祖滿級祝福:免疫生病,健康恢復 +50%',
|
||||
effects: {
|
||||
sicknessImmune: true,
|
||||
healthRecovery: 0.5
|
||||
|
||||
|
||||
// 向後兼容的舊配置 (數值降低)
|
||||
favorLevelBuffs: {
|
||||
interval: 10,
|
||||
buffsPerLevel: {
|
||||
str: 0.1,
|
||||
health: 0.2
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -40,154 +81,6 @@ export const DEITIES = [
|
|||
icon: 'deity-mazu',
|
||||
|
||||
// 可求的簽詩類型
|
||||
lotTypes: ['guanyin_100'] // 觀音100籤
|
||||
},
|
||||
{
|
||||
id: 'earthgod',
|
||||
name: '土地公',
|
||||
personality: '親切和藹',
|
||||
buffs: {
|
||||
dropRate: 0.2,
|
||||
resourceGain: 0.15
|
||||
},
|
||||
buffDescriptions: ['掉落率 +20%', '資源獲得 +15%'],
|
||||
|
||||
favorLevelBuffs: {
|
||||
interval: 10,
|
||||
buffsPerLevel: {
|
||||
luck: 0.3, // 每級 +0.3 運勢
|
||||
str: 0.3 // 每級 +0.3 力量
|
||||
}
|
||||
},
|
||||
|
||||
maxFavorBuff: {
|
||||
id: 'earthgod_prosperity',
|
||||
name: '土地公賜福',
|
||||
description: '土地公滿級祝福:掉落率 +50%,資源獲得 +30%',
|
||||
effects: {
|
||||
dropRate: 0.5,
|
||||
resourceGain: 0.3
|
||||
}
|
||||
},
|
||||
|
||||
dialogues: [
|
||||
'土地公保佑,財源廣進',
|
||||
'好好照顧寵物,會有福報的',
|
||||
'心善之人,必有善報'
|
||||
],
|
||||
icon: 'deity-earthgod',
|
||||
|
||||
lotTypes: ['guanyin_100'] // 暫用觀音100籤
|
||||
},
|
||||
{
|
||||
id: 'yuelao',
|
||||
name: '月老',
|
||||
personality: '浪漫溫和',
|
||||
buffs: {
|
||||
happinessRecovery: 0.3,
|
||||
breedingSuccess: 0.2
|
||||
},
|
||||
buffDescriptions: ['快樂恢復 +30%', '繁殖成功率 +20%'],
|
||||
|
||||
favorLevelBuffs: {
|
||||
interval: 10,
|
||||
buffsPerLevel: {
|
||||
happiness: 0.5, // 每級 +0.5 基礎快樂
|
||||
dex: 0.4 // 每級 +0.4 敏捷
|
||||
}
|
||||
},
|
||||
|
||||
maxFavorBuff: {
|
||||
id: 'yuelao_eternal_love',
|
||||
name: '月老牽線',
|
||||
description: '月老滿級祝福:快樂恢復 +60%,繁殖成功率 +50%',
|
||||
effects: {
|
||||
happinessRecovery: 0.6,
|
||||
breedingSuccess: 0.5
|
||||
}
|
||||
},
|
||||
|
||||
dialogues: [
|
||||
'月老牽線,姻緣天定',
|
||||
'好姻緣需要緣份',
|
||||
'真心相待,自有佳偶'
|
||||
],
|
||||
icon: 'deity-yuelao',
|
||||
|
||||
lotTypes: ['guanyin_100'] // 暫用觀音100籤
|
||||
},
|
||||
{
|
||||
id: 'wenchang',
|
||||
name: '文昌',
|
||||
personality: '智慧嚴謹',
|
||||
buffs: {
|
||||
intGain: 0.25,
|
||||
miniGameBonus: 0.15
|
||||
},
|
||||
buffDescriptions: ['智力成長 +25%', '小遊戲獎勵 +15%'],
|
||||
|
||||
favorLevelBuffs: {
|
||||
interval: 10,
|
||||
buffsPerLevel: {
|
||||
int: 0.6, // 每級 +0.6 智力
|
||||
dex: 0.2 // 每級 +0.2 敏捷
|
||||
}
|
||||
},
|
||||
|
||||
maxFavorBuff: {
|
||||
id: 'wenchang_supreme_wisdom',
|
||||
name: '文昌賜智',
|
||||
description: '文昌滿級祝福:智力成長 +60%,小遊戲獎勵 +40%',
|
||||
effects: {
|
||||
intGain: 0.6,
|
||||
miniGameBonus: 0.4
|
||||
}
|
||||
},
|
||||
|
||||
dialogues: [
|
||||
'勤學不辟,智慧增長',
|
||||
'好好學習,寵物也會變聰明',
|
||||
'知識就是力量'
|
||||
],
|
||||
icon: 'deity-wenchang',
|
||||
|
||||
lotTypes: ['guanyin_100'] // 暫用觀音100籤
|
||||
},
|
||||
{
|
||||
id: 'guanyin',
|
||||
name: '觀音',
|
||||
personality: '慈悲寬容',
|
||||
buffs: {
|
||||
healthRecovery: 0.2,
|
||||
badEventReduction: 0.15
|
||||
},
|
||||
buffDescriptions: ['健康恢復 +20%', '壞事件機率 -15%'],
|
||||
|
||||
favorLevelBuffs: {
|
||||
interval: 10,
|
||||
buffsPerLevel: {
|
||||
health: 1.5, // 每級 +1.5 最大健康
|
||||
int: 0.3 // 每級 +0.3 智力
|
||||
}
|
||||
},
|
||||
|
||||
maxFavorBuff: {
|
||||
id: 'guanyin_mercy',
|
||||
name: '觀音慈悲',
|
||||
description: '觀音滿級祝福:健康恢復 +50%,壞事件機率 -40%',
|
||||
effects: {
|
||||
healthRecovery: 0.5,
|
||||
badEventReduction: 0.4
|
||||
}
|
||||
},
|
||||
|
||||
dialogues: [
|
||||
'慈悲為懷,萬物皆靈',
|
||||
'好好照顧,觀音會保佑',
|
||||
'心存善念,受佛保佑'
|
||||
],
|
||||
icon: 'deity-guanyin',
|
||||
|
||||
lotTypes: ['guanyin_100'] // 觀音100籤
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// 神明進化任務類型定義
|
||||
export const QUEST_TYPES = {
|
||||
JIAOBEI: 'jiaobei', // 擲筊 (累積聖杯次數)
|
||||
CLEAN: 'clean', // 清理便便
|
||||
FEED: 'feed', // 餵食
|
||||
PLAY: 'play', // 玩耍
|
||||
COLLECT_COINS: 'collect_coins', // 獲得金幣
|
||||
HAPPINESS_MAX: 'happiness_max', // 快樂值達到滿值
|
||||
MINIGAME_WIN: 'minigame_win', // 小遊戲勝利
|
||||
INT_STAT: 'int_stat', // 智力達到數值
|
||||
HEAL: 'heal', // 治療
|
||||
NO_SICK: 'no_sick', // 連續不生病 (天數)
|
||||
TAROT_READING: 'tarot_reading' // 塔羅占卜
|
||||
}
|
||||
|
||||
// 檢查任務進度
|
||||
// currentProgress: 當前進度數值
|
||||
// target: 目標數值
|
||||
// actionType: 當前觸發的動作類型
|
||||
// questType: 任務要求的類型
|
||||
// value: 動作產生的數值 (例如獲得的金幣量)
|
||||
export const checkQuestProgress = (currentProgress, target, actionType, questType, value = 1) => {
|
||||
if (actionType !== questType) return currentProgress
|
||||
|
||||
const newProgress = currentProgress + value
|
||||
return Math.min(newProgress, target)
|
||||
}
|
||||
|
||||
// 格式化任務描述
|
||||
export const formatQuestProgress = (quest, currentProgress) => {
|
||||
return `${currentProgress} / ${quest.target}`
|
||||
}
|
||||
|
|
@ -9,23 +9,23 @@ export const PET_SPECIES = {
|
|||
physiologyTickInterval: 1000, // 生理系統刷新間隔:1秒 (更流暢的視覺效果)
|
||||
eventCheckInterval: 10000, // 事件檢查間隔:10秒
|
||||
|
||||
// 衰減速率 (每 tick 1秒)
|
||||
// 調整為:飢餓 8 小時,快樂 5 小時 (數值除以 10)
|
||||
hungerDecayPerTick: 0.02, // 原 0.2 (10秒) → 0.02 (1秒)
|
||||
happinessDecayPerTick: 0.033, // 原 0.33 (10秒) → 0.033 (1秒)
|
||||
// 衰減速率 (每 tick 1秒) - 調整為更輕鬆的體驗
|
||||
// 飢餓從 8 小時延長到 16 小時,快樂從 5 小時延長到 10 小時
|
||||
hungerDecayPerTick: 0.01, // 降低!0.02 → 0.01 (每秒掉 0.01,100/0.01/60 = 約 166 分鐘 ~16小時)
|
||||
happinessDecayPerTick: 0.017, // 降低!0.033 → 0.017 (每秒掉 0.017,約 10 小時)
|
||||
|
||||
// 便便系統
|
||||
poopChancePerTick: 0.05, // 約 20 分鐘產生一次
|
||||
poopHealthDamage: 0.5, // 大幅降低!每坨每分鐘只扣 0.5 (原 3.0)
|
||||
// 便便系統 - 大幅降低頻率和傷害
|
||||
poopChancePerTick: 0.025, // 降低!0.05 → 0.025 (約 40 分鐘產生一次,原本 20 分鐘)
|
||||
poopHealthDamage: 0.2, // 降低!0.5 → 0.2 (每坨每分鐘只扣 0.2,原本 0.5)
|
||||
|
||||
// 飢餓系統
|
||||
hungerHealthDamage: 1, // 大幅降低!餓肚子每分鐘只扣 1 (原 6.0)
|
||||
// 飢餓系統 - 降低傷害
|
||||
hungerHealthDamage: 0.3, // 降低!1 → 0.3 (餓肚子每分鐘只扣 0.3,原本 1)
|
||||
|
||||
// 生病系統
|
||||
sicknessThreshold: 40, // 健康低於 40 會生病
|
||||
// 生病系統 - 更不容易生病
|
||||
sicknessThreshold: 20, // 降低!40 → 20 (健康低於 20 才會生病,原本 40)
|
||||
|
||||
// 瀕死系統
|
||||
dyingTimeSeconds: 7200, // 瀕死 2 小時後死亡
|
||||
// 瀕死系統 - 延長時間
|
||||
dyingTimeSeconds: 14400, // 延長!7200 → 14400 (瀕死 4 小時後死亡,原本 2 小時)
|
||||
|
||||
// 睡眠系統配置
|
||||
sleepSchedule: {
|
||||
|
|
@ -225,13 +225,13 @@ export const PET_SPECIES = {
|
|||
baseStats: {
|
||||
physiologyTickInterval: 1000,
|
||||
eventCheckInterval: 10000,
|
||||
hungerDecayPerTick: 0.025, // 狗狗比較容易餓 (原 0.02)
|
||||
happinessDecayPerTick: 0.02, // 狗狗比較容易開心 (原 0.033)
|
||||
poopChancePerTick: 0.06, // 狗狗比較容易便便
|
||||
poopHealthDamage: 0.5,
|
||||
hungerHealthDamage: 1,
|
||||
sicknessThreshold: 40,
|
||||
dyingTimeSeconds: 7200,
|
||||
hungerDecayPerTick: 0.0125, // 降低!0.025 → 0.0125 (狗狗稍微容易餓,但也減半)
|
||||
happinessDecayPerTick: 0.01, // 降低!0.02 → 0.01 (狗狗比較容易開心)
|
||||
poopChancePerTick: 0.03, // 降低!0.06 → 0.03 (約 33 分鐘一次,原本 17 分鐘)
|
||||
poopHealthDamage: 0.2, // 降低!0.5 → 0.2
|
||||
hungerHealthDamage: 0.3, // 降低!1 → 0.3
|
||||
sicknessThreshold: 20, // 降低!40 → 20
|
||||
dyingTimeSeconds: 14400, // 延長!7200 → 14400 (4 小時)
|
||||
sleepSchedule: {
|
||||
nightSleep: {
|
||||
startHour: 22, // 晚上 22:00 開始 (比貓晚睡)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// 塔羅牌大阿爾克那 (Major Arcana)
|
||||
export const TAROT_MAJOR_ARCANA = [
|
||||
{ id: 0, name: '愚者', nameEn: 'The Fool', meaning: '新的開始、冒險、天真、潛力', description: '踏上未知的旅程,充滿無限可能。', icon: 'tarot-0' },
|
||||
{ id: 1, name: '魔術師', nameEn: 'The Magician', meaning: '創造力、自信、意志力、技能', description: '掌握資源,將夢想轉化為現實。', icon: 'tarot-1' },
|
||||
{ id: 2, name: '女祭司', nameEn: 'The High Priestess', meaning: '直覺、神秘、潛意識、智慧', description: '傾聽內心的聲音,信任你的直覺。', icon: 'tarot-2' },
|
||||
{ id: 3, name: '皇后', nameEn: 'The Empress', meaning: '豐饒、母性、自然、感官享受', description: '享受生活的富足與創造力。', icon: 'tarot-3' },
|
||||
{ id: 4, name: '皇帝', nameEn: 'The Emperor', meaning: '權威、結構、控制、父親形象', description: '建立秩序與穩定,展現領導力。', icon: 'tarot-4' },
|
||||
{ id: 5, name: '教皇', nameEn: 'The Hierophant', meaning: '傳統、信仰、教育、精神指引', description: '尋求智慧與傳統的指引。', icon: 'tarot-5' },
|
||||
{ id: 6, name: '戀人', nameEn: 'The Lovers', meaning: '愛、和諧、關係、價值觀選擇', description: '做出發自內心的選擇,建立深刻連結。', icon: 'tarot-6' },
|
||||
{ id: 7, name: '戰車', nameEn: 'The Chariot', meaning: '勝利、意志力、決心、自律', description: '勇往直前,克服一切障礙。', icon: 'tarot-7' },
|
||||
{ id: 8, name: '力量', nameEn: 'Strength', meaning: '勇氣、耐心、控制、同情心', description: '以柔克剛,展現內在的力量。', icon: 'tarot-8' },
|
||||
{ id: 9, name: '隱士', nameEn: 'The Hermit', meaning: '內省、孤獨、尋求真理、指引', description: '暫時退隱,尋找內在的光芒。', icon: 'tarot-9' },
|
||||
{ id: 10, name: '命運之輪', nameEn: 'Wheel of Fortune', meaning: '改變、週期、命運、轉折點', description: '順應生命的流動,把握轉機。', icon: 'tarot-10' },
|
||||
{ id: 11, name: '正義', nameEn: 'Justice', meaning: '公正、真理、因果、法律', description: '理性判斷,承擔行為的後果。', icon: 'tarot-11' },
|
||||
{ id: 12, name: '倒吊人', nameEn: 'The Hanged Man', meaning: '犧牲、放手、新視角、等待', description: '換個角度看世界,學會放手。', icon: 'tarot-12' },
|
||||
{ id: 13, name: '死神', nameEn: 'Death', meaning: '結束、轉變、重生、過渡', description: '告別過去,迎接新的開始。', icon: 'tarot-13' },
|
||||
{ id: 14, name: '節制', nameEn: 'Temperance', meaning: '平衡、適度、耐心、目的', description: '尋求平衡與和諧,避免極端。', icon: 'tarot-14' },
|
||||
{ id: 15, name: '惡魔', nameEn: 'The Devil', meaning: '束縛、物質主義、誘惑、陰影', description: '正視內心的恐懼與慾望,尋求自由。', icon: 'tarot-15' },
|
||||
{ id: 16, name: '高塔', nameEn: 'The Tower', meaning: '突變、混亂、啟示、覺醒', description: '舊有的結構崩塌,為真理騰出空間。', icon: 'tarot-16' },
|
||||
{ id: 17, name: '星星', nameEn: 'The Star', meaning: '希望、信仰、靈感、治癒', description: '保持希望,跟隨指引之星。', icon: 'tarot-17' },
|
||||
{ id: 18, name: '月亮', nameEn: 'The Moon', meaning: '幻覺、恐懼、潛意識、直覺', description: '面對未知的恐懼,信任內在直覺。', icon: 'tarot-18' },
|
||||
{ id: 19, name: '太陽', nameEn: 'The Sun', meaning: '快樂、成功、活力、慶祝', description: '享受陽光與溫暖,展現真實自我。', icon: 'tarot-19' },
|
||||
{ id: 20, name: '審判', nameEn: 'Judgement', meaning: '評判、重生、內在召喚、寬恕', description: '回應內心的召喚,做出重要決定。', icon: 'tarot-20' },
|
||||
{ id: 21, name: '世界', nameEn: 'The World', meaning: '完成、整合、成就、旅行', description: '圓滿達成目標,開啟新的篇章。', icon: 'tarot-21' }
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 569 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 609 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 610 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Loading…
Reference in New Issue