good version
This commit is contained in:
parent
34adb9c15c
commit
e04677088a
153
src/App.vue
153
src/App.vue
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DeviceShell from './components/DeviceShell.vue';
|
||||
import DeviceScreen from './components/DeviceScreen.vue';
|
||||
import PetGame from './components/PetGame.vue';
|
||||
|
|
@ -14,25 +14,14 @@ const debugAction = ref(null); // For passing debug commands to PetGame
|
|||
|
||||
// Initialize Pet System
|
||||
const petSystem = usePetSystem();
|
||||
const {
|
||||
stage,
|
||||
state,
|
||||
stats,
|
||||
feed,
|
||||
play,
|
||||
sleep,
|
||||
clean,
|
||||
isCleaning,
|
||||
hatchEgg,
|
||||
reset,
|
||||
achievements,
|
||||
unlockAllAchievements,
|
||||
resurrect,
|
||||
reincarnate
|
||||
} = petSystem;
|
||||
|
||||
// Initialize Event System
|
||||
const { currentEvent, checkEventTriggers, triggerRandomEvent } = useEventSystem(petSystem);
|
||||
const { currentEvent, checkEventTriggers, triggerRandomEvent, loadEventConfig } = useEventSystem(petSystem);
|
||||
|
||||
onMounted(async () => {
|
||||
await petSystem.initGame();
|
||||
await loadEventConfig(); // Load event configuration
|
||||
});
|
||||
|
||||
// Start Event Loop
|
||||
setInterval(() => {
|
||||
|
|
@ -41,41 +30,40 @@ setInterval(() => {
|
|||
|
||||
|
||||
// Handle Action Menu Events
|
||||
function handleAction(action) {
|
||||
async function handleAction(action) {
|
||||
switch(action) {
|
||||
case 'feed':
|
||||
const feedResult = feed();
|
||||
const feedResult = await petSystem.feed();
|
||||
// If refused (sick or full), shake head
|
||||
if (!feedResult && petGameRef.value) {
|
||||
petGameRef.value.shakeHead();
|
||||
}
|
||||
break;
|
||||
case 'clean':
|
||||
clean();
|
||||
await petSystem.clean();
|
||||
break;
|
||||
case 'play':
|
||||
if (play()) {
|
||||
if (petGameRef.value) {
|
||||
const playResult = await petSystem.play();
|
||||
if (playResult && petGameRef.value) {
|
||||
petGameRef.value.startPlaying();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sleep':
|
||||
sleep();
|
||||
await petSystem.sleep();
|
||||
break;
|
||||
case 'medicine':
|
||||
// Heal the pet with animation
|
||||
if (state.value === 'sick') {
|
||||
if (petSystem.state.value === 'sick') {
|
||||
if (petGameRef.value) {
|
||||
// Trigger medicine animation
|
||||
petGameRef.value.startFeeding('medicine').then(() => {
|
||||
stats.value.health = 100;
|
||||
state.value = 'idle';
|
||||
petSystem.stats.value.health = 100;
|
||||
petSystem.state.value = 'idle';
|
||||
});
|
||||
} else {
|
||||
// Fallback if ref not ready
|
||||
stats.value.health = 100;
|
||||
state.value = 'idle';
|
||||
petSystem.stats.value.health = 100;
|
||||
petSystem.state.value = 'idle';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -85,10 +73,10 @@ function handleAction(action) {
|
|||
break;
|
||||
case 'settings':
|
||||
// Show reset options
|
||||
if (stage.value === 'egg') {
|
||||
hatchEgg();
|
||||
if (petSystem.stage.value === 'egg') {
|
||||
await petSystem.hatchEgg();
|
||||
} else {
|
||||
reset();
|
||||
await petSystem.reset();
|
||||
}
|
||||
break;
|
||||
case 'jiaobei':
|
||||
|
|
@ -102,19 +90,20 @@ function handleAction(action) {
|
|||
// TODO: 實作求籤邏輯
|
||||
break;
|
||||
case 'resurrect':
|
||||
resurrect();
|
||||
await petSystem.resurrect();
|
||||
break;
|
||||
case 'reincarnate':
|
||||
reincarnate();
|
||||
await petSystem.reincarnate();
|
||||
break;
|
||||
default:
|
||||
console.log('Action not implemented:', action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Debug/Dev Controls
|
||||
function setPetState(newState) {
|
||||
state.value = newState;
|
||||
petSystem.state.value = newState;
|
||||
}
|
||||
|
||||
function triggerDebugAction(action, payload = null) {
|
||||
|
|
@ -123,21 +112,34 @@ function triggerDebugAction(action, payload = null) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<DeviceShell>
|
||||
<!-- Debug: {{ petSystem.isLoading?.value }} - {{ petSystem.error?.value }} -->
|
||||
<div v-if="petSystem.isLoading?.value" class="loading-screen">
|
||||
<div class="loading-content">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading Game Data...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="petSystem.error?.value" class="error-screen">
|
||||
<p>Error: {{ petSystem.error.value }}</p>
|
||||
<button @click="petSystem.initGame()">Retry</button>
|
||||
</div>
|
||||
|
||||
<DeviceShell v-else>
|
||||
<DeviceScreen>
|
||||
<!-- Dynamic Component Switching -->
|
||||
<PetGame
|
||||
v-if="currentScreen === 'game'"
|
||||
ref="petGameRef"
|
||||
:state="state"
|
||||
:stage="stage"
|
||||
:stats="stats"
|
||||
:isCleaning="isCleaning"
|
||||
:state="petSystem.state"
|
||||
:stage="petSystem.stage"
|
||||
:stats="petSystem.stats"
|
||||
:isCleaning="petSystem.isCleaning"
|
||||
:showStats="showStats"
|
||||
:debugAction="debugAction"
|
||||
:achievements="achievements"
|
||||
:achievements="petSystem.achievements"
|
||||
:currentEvent="currentEvent"
|
||||
@update:state="state = $event"
|
||||
@update:state="petSystem.state = $event"
|
||||
@action="handleAction"
|
||||
/>
|
||||
<Menu v-else />
|
||||
|
|
@ -155,12 +157,12 @@ function triggerDebugAction(action, payload = null) {
|
|||
<button @click="setPetState('dead')">Dead</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button @click="stats.poopCount = Math.min((stats.poopCount || 0) + 1, 4)">💩 Add Poop</button>
|
||||
<button @click="stats.poopCount = 0">🧼 Clean All</button>
|
||||
<button @click="petSystem.stats.poopCount = Math.min((petSystem.stats.poopCount || 0) + 1, 4)">💩 Add Poop</button>
|
||||
<button @click="petSystem.stats.poopCount = 0">🧼 Clean All</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button v-if="stage === 'egg'" @click="hatchEgg()">🥚 Hatch Egg</button>
|
||||
<button v-else @click="reset()">🔄 Reset to Egg</button>
|
||||
<button v-if="petSystem.stage === 'egg'" @click="petSystem.hatchEgg()">🥚 Hatch Egg</button>
|
||||
<button v-else @click="petSystem.reset()">🔄 Reset to Egg</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button @click="triggerDebugAction('randomEvent')">🎲 Random Event</button>
|
||||
|
|
@ -173,16 +175,69 @@ function triggerDebugAction(action, payload = null) {
|
|||
<button @click="triggerDebugAction('setMood', 'sad')">😢 Sad</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button @click="unlockAllAchievements()">🏆 Unlock All Achievements</button>
|
||||
<button @click="petSystem.unlockAllAchievements()">🏆 Unlock All Achievements</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button @click="stats.dailyPrayerCount = 0">🙏 Reset Prayer Count</button>
|
||||
<button @click="petSystem.stats.dailyPrayerCount = 0">🙏 Reset Prayer Count</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Loading Screen */
|
||||
.loading-screen,
|
||||
.error-screen {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.loading-content,
|
||||
.error-screen {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-content p,
|
||||
.error-screen p {
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.error-screen button {
|
||||
margin-top: 20px;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error-screen button:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* Debug Controls */
|
||||
.debug-controls {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="action-menu">
|
||||
<button class="icon-btn icon-clean" @click="$emit('clean')" :disabled="disabled || poopCount === 0" title="清理"></button>
|
||||
<button class="icon-btn icon-medicine" @click="$emit('medicine')" :disabled="disabled || !isSick" title="治療"></button>
|
||||
<button class="icon-btn icon-sleep" @click="$emit('sleep')" :disabled="disabled" title="關燈"></button>
|
||||
<button class="icon-btn icon-backpack" @click="$emit('inventory')" :disabled="disabled" title="背包"></button>
|
||||
<button class="icon-btn icon-clean" @click="$emit('clean')" :disabled="disabled || isSleeping || poopCount === 0" title="清理"></button>
|
||||
<button class="icon-btn icon-medicine" @click="$emit('medicine')" :disabled="disabled || isSleeping || !isSick" title="治療"></button>
|
||||
<button class="icon-btn icon-sleep" @click="$emit('sleep')" :disabled="disabled" :title="isSleeping ? '開燈' : '關燈'"></button>
|
||||
<button class="icon-btn icon-backpack" @click="$emit('inventory')" :disabled="disabled || isSleeping" title="背包"></button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -13,6 +13,10 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isSleeping: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
poopCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
|
@ -35,14 +39,14 @@ defineEmits(['clean', 'medicine', 'sleep', 'inventory']);
|
|||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
padding: 10px 19px;
|
||||
background: rgba(155, 188, 15, 0.05);
|
||||
border-top: 2px solid rgba(0, 0, 0, 0.1);
|
||||
border-top: 3px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
|
@ -55,87 +59,112 @@ defineEmits(['clean', 'medicine', 'sleep', 'inventory']);
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Clean Icon (Broom) */
|
||||
/* Clean Icon (精美掃把) */
|
||||
.icon-clean::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 3.2px;
|
||||
height: 3.2px;
|
||||
background: #8B4513;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow:
|
||||
0px -6px 0 #8B4513, 0px -4px 0 #8B4513,
|
||||
0px -2px 0 #8B4513, 0px 0px 0 #8B4513,
|
||||
-4px 2px 0 #ffcc00, -2px 2px 0 #ffcc00, 0px 2px 0 #ffcc00, 2px 2px 0 #ffcc00, 4px 2px 0 #ffcc00,
|
||||
-4px 4px 0 #ffcc00, -2px 4px 0 #ffcc00, 0px 4px 0 #ffcc00, 2px 4px 0 #ffcc00, 4px 4px 0 #ffcc00,
|
||||
-2px 6px 0 #ffcc00, 0px 6px 0 #ffcc00, 2px 6px 0 #ffcc00;
|
||||
/* 掃把柄 */
|
||||
0px -12.8px 0 #8B4513, 0px -9.6px 0 #8B4513, 0px -6.4px 0 #8B4513,
|
||||
0px -3.2px 0 #8B4513, 0px 0px 0 #8B4513,
|
||||
/* 掃把頭連接處 */
|
||||
-1.6px 3.2px 0 #8B4513, 0px 3.2px 0 #8B4513, 1.6px 3.2px 0 #8B4513,
|
||||
/* 掃把頭 (左側) */
|
||||
-9.6px 6.4px 0 #ffcc00, -8px 6.4px 0 #ffd700, -6.4px 6.4px 0 #ffd700, -4.8px 6.4px 0 #ffcc00, -3.2px 6.4px 0 #ffd700, -1.6px 6.4px 0 #ffcc00, 0px 6.4px 0 #ffd700,
|
||||
-9.6px 9.6px 0 #ffcc00, -8px 9.6px 0 #ffd700, -6.4px 9.6px 0 #ffd700, -4.8px 9.6px 0 #ffcc00, -3.2px 9.6px 0 #ffd700, -1.6px 9.6px 0 #ffcc00, 0px 9.6px 0 #ffd700,
|
||||
-8px 12.8px 0 #ffcc00, -6.4px 12.8px 0 #ffd700, -4.8px 12.8px 0 #ffcc00, -3.2px 12.8px 0 #ffd700, -1.6px 12.8px 0 #ffcc00, 0px 12.8px 0 #ffd700,
|
||||
-6.4px 16px 0 #ffcc00, -4.8px 16px 0 #ffd700, -3.2px 16px 0 #ffcc00, -1.6px 16px 0 #ffd700, 0px 16px 0 #ffcc00,
|
||||
/* 掃把頭 (右側) */
|
||||
1.6px 6.4px 0 #ffd700, 3.2px 6.4px 0 #ffcc00, 4.8px 6.4px 0 #ffd700, 6.4px 6.4px 0 #ffd700, 8px 6.4px 0 #ffcc00, 9.6px 6.4px 0 #ffd700,
|
||||
1.6px 9.6px 0 #ffd700, 3.2px 9.6px 0 #ffcc00, 4.8px 9.6px 0 #ffd700, 6.4px 9.6px 0 #ffd700, 8px 9.6px 0 #ffcc00, 9.6px 9.6px 0 #ffd700,
|
||||
1.6px 12.8px 0 #ffd700, 3.2px 12.8px 0 #ffcc00, 4.8px 12.8px 0 #ffd700, 6.4px 12.8px 0 #ffcc00, 8px 12.8px 0 #ffd700,
|
||||
1.6px 16px 0 #ffcc00, 3.2px 16px 0 #ffd700, 4.8px 16px 0 #ffcc00, 6.4px 16px 0 #ffd700;
|
||||
}
|
||||
|
||||
/* Medicine Icon (Pill/Cross) */
|
||||
/* Medicine Icon (精美藥丸) */
|
||||
.icon-medicine::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #ff4444;
|
||||
width: 3.2px;
|
||||
height: 3.2px;
|
||||
background: #ff6b6b;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow:
|
||||
0px -6px 0 #ff4444, 0px -4px 0 #ff4444,
|
||||
-4px -2px 0 #ff4444, -2px -2px 0 #ff4444, 0px -2px 0 #ff4444, 2px -2px 0 #ff4444, 4px -2px 0 #ff4444,
|
||||
0px 0px 0 #ff4444, 0px 2px 0 #ff4444,
|
||||
0px 4px 0 #ff4444, 0px 6px 0 #ff4444;
|
||||
/* 藥丸上半部 (圓形) */
|
||||
-3.2px -9.6px 0 #ff5252, -1.6px -9.6px 0 #ff6b6b, 0px -9.6px 0 #ff6b6b, 1.6px -9.6px 0 #ff6b6b, 3.2px -9.6px 0 #ff5252,
|
||||
-4.8px -6.4px 0 #ff5252, -3.2px -6.4px 0 #ff6b6b, -1.6px -6.4px 0 #ff8a80, 0px -6.4px 0 #ff6b6b, 1.6px -6.4px 0 #ff8a80, 3.2px -6.4px 0 #ff6b6b, 4.8px -6.4px 0 #ff5252,
|
||||
-4.8px -3.2px 0 #ff5252, -3.2px -3.2px 0 #ff6b6b, -1.6px -3.2px 0 #ff8a80, 0px -3.2px 0 #ff6b6b, 1.6px -3.2px 0 #ff8a80, 3.2px -3.2px 0 #ff6b6b, 4.8px -3.2px 0 #ff5252,
|
||||
/* 藥丸中間 (十字) */
|
||||
-1.6px 0px 0 #fff, 0px 0px 0 #fff, 1.6px 0px 0 #fff,
|
||||
0px -1.6px 0 #fff, 0px 1.6px 0 #fff,
|
||||
/* 藥丸下半部 (圓形) */
|
||||
-4.8px 3.2px 0 #ff5252, -3.2px 3.2px 0 #ff6b6b, -1.6px 3.2px 0 #ff8a80, 0px 3.2px 0 #ff6b6b, 1.6px 3.2px 0 #ff8a80, 3.2px 3.2px 0 #ff6b6b, 4.8px 3.2px 0 #ff5252,
|
||||
-4.8px 6.4px 0 #ff5252, -3.2px 6.4px 0 #ff6b6b, -1.6px 6.4px 0 #ff8a80, 0px 6.4px 0 #ff6b6b, 1.6px 6.4px 0 #ff8a80, 3.2px 6.4px 0 #ff6b6b, 4.8px 6.4px 0 #ff5252,
|
||||
-3.2px 9.6px 0 #ff5252, -1.6px 9.6px 0 #ff6b6b, 0px 9.6px 0 #ff6b6b, 1.6px 9.6px 0 #ff6b6b, 3.2px 9.6px 0 #ff5252;
|
||||
}
|
||||
|
||||
/* Sleep Icon (Light Bulb/燈泡) */
|
||||
/* Sleep Icon (精美燈泡) */
|
||||
.icon-sleep::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #ffd700; /* 燈泡顏色 */
|
||||
width: 3.2px;
|
||||
height: 3.2px;
|
||||
background: #ffd700;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow:
|
||||
/* 燈泡主體 (圓形) */
|
||||
-2px -6px 0 #ffd700, 0px -6px 0 #ffd700, 2px -6px 0 #ffd700,
|
||||
-4px -4px 0 #ffd700, -2px -4px 0 #ffd700, 0px -4px 0 #ffd700, 2px -4px 0 #ffd700, 4px -4px 0 #ffd700,
|
||||
-4px -2px 0 #ffd700, -2px -2px 0 #ffd700, 0px -2px 0 #ffd700, 2px -2px 0 #ffd700, 4px -2px 0 #ffd700,
|
||||
-4px 0px 0 #ffd700, -2px 0px 0 #ffd700, 0px 0px 0 #ffd700, 2px 0px 0 #ffd700, 4px 0px 0 #ffd700,
|
||||
-2px 2px 0 #ffd700, 0px 2px 0 #ffd700, 2px 2px 0 #ffd700,
|
||||
/* 燈泡主體 (圓形,帶高光) */
|
||||
-3.2px -12.8px 0 #ffb300, -1.6px -12.8px 0 #ffd700, 0px -12.8px 0 #ffd700, 1.6px -12.8px 0 #ffd700, 3.2px -12.8px 0 #ffb300,
|
||||
-6.4px -9.6px 0 #ffb300, -4.8px -9.6px 0 #ffd700, -3.2px -9.6px 0 #fff59d, -1.6px -9.6px 0 #ffd700, 0px -9.6px 0 #fff59d, 1.6px -9.6px 0 #ffd700, 3.2px -9.6px 0 #fff59d, 4.8px -9.6px 0 #ffd700, 6.4px -9.6px 0 #ffb300,
|
||||
-6.4px -6.4px 0 #ffb300, -4.8px -6.4px 0 #ffd700, -3.2px -6.4px 0 #fff59d, -1.6px -6.4px 0 #ffd700, 0px -6.4px 0 #fff59d, 1.6px -6.4px 0 #ffd700, 3.2px -6.4px 0 #fff59d, 4.8px -6.4px 0 #ffd700, 6.4px -6.4px 0 #ffb300,
|
||||
-6.4px -3.2px 0 #ffb300, -4.8px -3.2px 0 #ffd700, -3.2px -3.2px 0 #fff59d, -1.6px -3.2px 0 #ffd700, 0px -3.2px 0 #fff59d, 1.6px -3.2px 0 #ffd700, 3.2px -3.2px 0 #fff59d, 4.8px -3.2px 0 #ffd700, 6.4px -3.2px 0 #ffb300,
|
||||
-6.4px 0px 0 #ffb300, -4.8px 0px 0 #ffd700, -3.2px 0px 0 #fff59d, -1.6px 0px 0 #ffd700, 0px 0px 0 #fff59d, 1.6px 0px 0 #ffd700, 3.2px 0px 0 #fff59d, 4.8px 0px 0 #ffd700, 6.4px 0px 0 #ffb300,
|
||||
-4.8px 3.2px 0 #ffb300, -3.2px 3.2px 0 #ffd700, -1.6px 3.2px 0 #ffd700, 0px 3.2px 0 #ffd700, 1.6px 3.2px 0 #ffd700, 3.2px 3.2px 0 #ffd700, 4.8px 3.2px 0 #ffb300,
|
||||
-3.2px 6.4px 0 #ffb300, -1.6px 6.4px 0 #ffd700, 0px 6.4px 0 #ffd700, 1.6px 6.4px 0 #ffd700, 3.2px 6.4px 0 #ffb300,
|
||||
/* 燈泡底部 (螺旋) */
|
||||
-1px 4px 0 #8B4513, 0px 4px 0 #8B4513, 1px 4px 0 #8B4513,
|
||||
/* 光線 (向下) */
|
||||
0px 6px 0 #ffd700, 0px 8px 0 #ffd700;
|
||||
-3.2px 9.6px 0 #8B4513, -1.6px 9.6px 0 #654321, 0px 9.6px 0 #8B4513, 1.6px 9.6px 0 #654321, 3.2px 9.6px 0 #8B4513,
|
||||
-1.6px 12.8px 0 #8B4513, 0px 12.8px 0 #654321, 1.6px 12.8px 0 #8B4513,
|
||||
/* 光線 (向下發散) */
|
||||
-3.2px 16px 0 #ffd700, -1.6px 16px 0 #ffd700, 0px 16px 0 #ffd700, 1.6px 16px 0 #ffd700, 3.2px 16px 0 #ffd700,
|
||||
-1.6px 19.2px 0 #ffd700, 0px 19.2px 0 #ffd700, 1.6px 19.2px 0 #ffd700,
|
||||
0px 22.4px 0 #ffd700;
|
||||
}
|
||||
|
||||
/* Backpack Icon */
|
||||
/* Backpack Icon (精美背包) */
|
||||
.icon-backpack::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 3.2px;
|
||||
height: 3.2px;
|
||||
background: #8d6e63;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow:
|
||||
/* Top flap */
|
||||
-2px -6px 0 #8d6e63, 0px -6px 0 #8d6e63, 2px -6px 0 #8d6e63,
|
||||
-4px -4px 0 #8d6e63, -2px -4px 0 #a1887f, 0px -4px 0 #a1887f, 2px -4px 0 #a1887f, 4px -4px 0 #8d6e63,
|
||||
|
||||
/* Body */
|
||||
-4px -2px 0 #8d6e63, -2px -2px 0 #5d4037, 0px -2px 0 #5d4037, 2px -2px 0 #5d4037, 4px -2px 0 #8d6e63,
|
||||
-4px 0px 0 #8d6e63, -2px 0px 0 #5d4037, 0px 0px 0 #5d4037, 2px 0px 0 #5d4037, 4px 0px 0 #8d6e63,
|
||||
-4px 2px 0 #8d6e63, -2px 2px 0 #5d4037, 0px 2px 0 #5d4037, 2px 2px 0 #5d4037, 4px 2px 0 #8d6e63,
|
||||
-4px 4px 0 #8d6e63, -2px 4px 0 #5d4037, 0px 4px 0 #5d4037, 2px 4px 0 #5d4037, 4px 4px 0 #8d6e63,
|
||||
|
||||
/* Bottom */
|
||||
-2px 6px 0 #8d6e63, 0px 6px 0 #8d6e63, 2px 6px 0 #8d6e63;
|
||||
/* 頂部翻蓋 */
|
||||
-4.8px -12.8px 0 #8d6e63, -3.2px -12.8px 0 #a1887f, -1.6px -12.8px 0 #8d6e63, 0px -12.8px 0 #a1887f, 1.6px -12.8px 0 #8d6e63, 3.2px -12.8px 0 #a1887f, 4.8px -12.8px 0 #8d6e63,
|
||||
-6.4px -9.6px 0 #8d6e63, -4.8px -9.6px 0 #a1887f, -3.2px -9.6px 0 #bcaaa4, -1.6px -9.6px 0 #a1887f, 0px -9.6px 0 #bcaaa4, 1.6px -9.6px 0 #a1887f, 3.2px -9.6px 0 #bcaaa4, 4.8px -9.6px 0 #a1887f, 6.4px -9.6px 0 #8d6e63,
|
||||
/* 背包主體 */
|
||||
-6.4px -6.4px 0 #8d6e63, -4.8px -6.4px 0 #5d4037, -3.2px -6.4px 0 #5d4037, -1.6px -6.4px 0 #5d4037, 0px -6.4px 0 #5d4037, 1.6px -6.4px 0 #5d4037, 3.2px -6.4px 0 #5d4037, 4.8px -6.4px 0 #5d4037, 6.4px -6.4px 0 #8d6e63,
|
||||
-6.4px -3.2px 0 #8d6e63, -4.8px -3.2px 0 #5d4037, -3.2px -3.2px 0 #6d4c41, -1.6px -3.2px 0 #5d4037, 0px -3.2px 0 #6d4c41, 1.6px -3.2px 0 #5d4037, 3.2px -3.2px 0 #6d4c41, 4.8px -3.2px 0 #5d4037, 6.4px -3.2px 0 #8d6e63,
|
||||
-6.4px 0px 0 #8d6e63, -4.8px 0px 0 #5d4037, -3.2px 0px 0 #6d4c41, -1.6px 0px 0 #5d4037, 0px 0px 0 #6d4c41, 1.6px 0px 0 #5d4037, 3.2px 0px 0 #6d4c41, 4.8px 0px 0 #5d4037, 6.4px 0px 0 #8d6e63,
|
||||
-6.4px 3.2px 0 #8d6e63, -4.8px 3.2px 0 #5d4037, -3.2px 3.2px 0 #6d4c41, -1.6px 3.2px 0 #5d4037, 0px 3.2px 0 #6d4c41, 1.6px 3.2px 0 #5d4037, 3.2px 3.2px 0 #6d4c41, 4.8px 3.2px 0 #5d4037, 6.4px 3.2px 0 #8d6e63,
|
||||
-6.4px 6.4px 0 #8d6e63, -4.8px 6.4px 0 #5d4037, -3.2px 6.4px 0 #5d4037, -1.6px 6.4px 0 #5d4037, 0px 6.4px 0 #5d4037, 1.6px 6.4px 0 #5d4037, 3.2px 6.4px 0 #5d4037, 4.8px 6.4px 0 #5d4037, 6.4px 6.4px 0 #8d6e63,
|
||||
/* 底部 */
|
||||
-4.8px 9.6px 0 #8d6e63, -3.2px 9.6px 0 #5d4037, -1.6px 9.6px 0 #5d4037, 0px 9.6px 0 #5d4037, 1.6px 9.6px 0 #5d4037, 3.2px 9.6px 0 #5d4037, 4.8px 9.6px 0 #8d6e63,
|
||||
-3.2px 12.8px 0 #8d6e63, -1.6px 12.8px 0 #5d4037, 0px 12.8px 0 #5d4037, 1.6px 12.8px 0 #5d4037, 3.2px 12.8px 0 #8d6e63,
|
||||
/* 側邊帶子 */
|
||||
-8px -3.2px 0 #8d6e63, -8px 0px 0 #8d6e63, -8px 3.2px 0 #8d6e63,
|
||||
8px -3.2px 0 #8d6e63, 8px 0px 0 #8d6e63, 8px 3.2px 0 #8d6e63;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,49 +17,49 @@
|
|||
<style scoped>
|
||||
/* 外殼 */
|
||||
.device {
|
||||
width: 280px;
|
||||
height: 340px;
|
||||
width: 448px;
|
||||
height: 544px;
|
||||
border-radius: 60% 60% 55% 55%;
|
||||
background: radial-gradient(circle at 30% 0%, #ffe7ff, #ffc6f1);
|
||||
box-shadow:
|
||||
0 18px 40px rgba(0, 0, 0, 0.35),
|
||||
inset 0 4px 10px rgba(255, 255, 255, 0.8);
|
||||
padding-top: 38px;
|
||||
0 29px 64px rgba(0, 0, 0, 0.35),
|
||||
inset 0 6px 16px rgba(255, 255, 255, 0.8);
|
||||
padding-top: 61px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
gap: 29px;
|
||||
}
|
||||
|
||||
.device-title {
|
||||
font-size: 14px;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.screen-wrapper {
|
||||
width: 210px;
|
||||
height: 160px;
|
||||
border-radius: 18px;
|
||||
width: 336px;
|
||||
height: 256px;
|
||||
border-radius: 29px;
|
||||
background: #888;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
padding: 6px;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 4px;
|
||||
gap: 26px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 20%, #fff8e7, #f3a3af);
|
||||
box-shadow:
|
||||
0 4px 0 #c26c7a,
|
||||
0 4px 8px rgba(0, 0, 0, 0.35);
|
||||
0 6px 0 #c26c7a,
|
||||
0 6px 13px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<!-- Top Menu -->
|
||||
<TopMenu
|
||||
:disabled="stage === 'egg'"
|
||||
:isSleeping="state === 'sleep'"
|
||||
@info="showPetInfo = !showPetInfo"
|
||||
@feed="$emit('action', 'feed')"
|
||||
@playMenu="showPlayMenu = true"
|
||||
|
|
@ -177,11 +178,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 關燈黑色遮罩 (原本 PrayerMenu 的位置) -->
|
||||
<div
|
||||
v-if="state === 'sleep'"
|
||||
class="dark-overlay-fullscreen"
|
||||
></div>
|
||||
|
||||
|
||||
<!-- Jiaobei Animation (覆蓋遊戲區域) -->
|
||||
<JiaobeiAnimation
|
||||
|
|
@ -282,6 +279,7 @@
|
|||
<!-- Action Menu (Bottom) -->
|
||||
<ActionMenu
|
||||
:disabled="stage === 'egg'"
|
||||
:isSleeping="state === 'sleep'"
|
||||
:poopCount="stats?.poopCount || 0"
|
||||
:health="stats?.health || 100"
|
||||
:isSick="state === 'sick'"
|
||||
|
|
@ -947,7 +945,7 @@ const FOOD_PALETTE = FOOD_OPTIONS[currentFood].palette;
|
|||
|
||||
const CURRENT_PRESET = SPRITE_PRESETS.tinyTigerCatB;
|
||||
const FULL_PRESET = FULL_PRESETS.tinyTigerCatB;
|
||||
const pixelSize = CURRENT_PRESET.pixelSize;
|
||||
const pixelSize = Math.round(CURRENT_PRESET.pixelSize * 1.6);
|
||||
|
||||
// Calculate base stats based on current stage
|
||||
const baseStats = computed(() => {
|
||||
|
|
@ -1570,8 +1568,8 @@ defineExpose({
|
|||
}
|
||||
@keyframes pet-sick-shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-2px); }
|
||||
75% { transform: translateX(2px); }
|
||||
25% { transform: translateX(-3.2px); }
|
||||
75% { transform: translateX(3.2px); }
|
||||
}
|
||||
|
||||
/* Tail */
|
||||
|
|
@ -1582,17 +1580,17 @@ defineExpose({
|
|||
|
||||
@keyframes tail-wag-idle {
|
||||
0% { transform: translateX(0px); }
|
||||
50% { transform: translateX(1px); }
|
||||
50% { transform: translateX(1.6px); }
|
||||
100% { transform: translateX(0px); }
|
||||
}
|
||||
@keyframes tail-wag-sleep {
|
||||
0% { transform: translateX(0px); }
|
||||
50% { transform: translateX(0.5px); }
|
||||
50% { transform: translateX(0.8px); }
|
||||
100% { transform: translateX(0px); }
|
||||
}
|
||||
@keyframes tail-wag-sick {
|
||||
0% { transform: translateX(0px); }
|
||||
50% { transform: translateX(0.8px); }
|
||||
50% { transform: translateX(1.3px); }
|
||||
100% { transform: translateX(0px); }
|
||||
}
|
||||
|
||||
|
|
@ -1604,15 +1602,15 @@ defineExpose({
|
|||
|
||||
@keyframes leg-front-step {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-1px); }
|
||||
50% { transform: translateY(-1.6px); }
|
||||
}
|
||||
@keyframes leg-back-step {
|
||||
0%, 100% { transform: translateY(-1px); }
|
||||
0%, 100% { transform: translateY(-1.6px); }
|
||||
50% { transform: translateY(0); }
|
||||
}
|
||||
@keyframes leg-sick-step {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-0.5px); }
|
||||
50% { transform: translateY(-0.8px); }
|
||||
}
|
||||
|
||||
/* Ears */
|
||||
|
|
@ -1622,7 +1620,7 @@ defineExpose({
|
|||
|
||||
@keyframes ear-twitch {
|
||||
0%, 90%, 100% { transform: translateY(0); }
|
||||
92% { transform: translateY(-1px); }
|
||||
92% { transform: translateY(-1.6px); }
|
||||
96% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
|
|
@ -1649,17 +1647,7 @@ defineExpose({
|
|||
border-radius: 10px; /* 與螢幕邊框一致 */
|
||||
}
|
||||
|
||||
/* Fullscreen Dark Overlay (原本 PrayerMenu 的位置) */
|
||||
.dark-overlay-fullscreen {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
z-index: 100; /* Same as PrayerMenu was */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Sleep ZZZ */
|
||||
.sleep-zzz {
|
||||
|
|
@ -1670,30 +1658,112 @@ defineExpose({
|
|||
z-index: 10; /* Above dark overlay */
|
||||
}
|
||||
.sleep-zzz.dark-mode {
|
||||
color: #fff !important; /* 強制白色:在黑色背景下變白色 */
|
||||
text-shadow: 0 0 4px rgba(255, 255, 255, 0.8); /* 添加發光效果讓它更明顯 */
|
||||
color: #fff; /* 在黑色背景下變白色 */
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
.sleep-zzz span {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
animation: zzz-float 3s ease-in-out infinite;
|
||||
}
|
||||
.sleep-zzz .z1 { left: 0; animation-delay: 0s; }
|
||||
.sleep-zzz .z2 { left: 8px; animation-delay: 0.8s; }
|
||||
.sleep-zzz .z3 { left: 15px; animation-delay: 1.6s; }
|
||||
.sleep-zzz .z2 { left: 13px; animation-delay: 0.8s; }
|
||||
.sleep-zzz .z3 { left: 24px; animation-delay: 1.6s; }
|
||||
|
||||
@keyframes zzz-float {
|
||||
0% { opacity: 0; transform: translateY(0) scale(0.7); }
|
||||
20% { opacity: 1; transform: translateY(-4px) scale(0.9); }
|
||||
80% { opacity: 1; transform: translateY(-16px) scale(1.05); }
|
||||
100% { opacity: 0; transform: translateY(-24px) scale(1.1); }
|
||||
20% { opacity: 1; transform: translateY(-6.4px) scale(0.9); }
|
||||
80% { opacity: 1; transform: translateY(-25.6px) scale(1.05); }
|
||||
100% { opacity: 0; transform: translateY(-38.4px) scale(1.1); }
|
||||
}
|
||||
|
||||
/* Event Bubble */
|
||||
.event-bubble {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -100%);
|
||||
background: white;
|
||||
border: 3px solid #333;
|
||||
border-radius: 13px;
|
||||
padding: 6px 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
z-index: 20;
|
||||
min-width: 96px;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.2);
|
||||
animation: bubble-pop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.event-bubble::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 10px 10px 0;
|
||||
border-style: solid;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.event-bubble::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 8px 8px 0;
|
||||
border-style: solid;
|
||||
border-color: white transparent transparent transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.event-text {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Event Animation */
|
||||
.event-animation {
|
||||
position: absolute;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
z-index: 15;
|
||||
pointer-events: none;
|
||||
animation: event-float 2s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Play Ball */
|
||||
.play-ball {
|
||||
position: absolute;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.ball-pixel {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #ff5252;
|
||||
box-shadow:
|
||||
6px 0 0 #ff5252,
|
||||
0 6px 0 #ff5252,
|
||||
6px 6px 0 #ff5252;
|
||||
}
|
||||
/* Sick Icon */
|
||||
.sick-icon {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
font-size: 22px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
animation: sick-icon-pulse 1.2s ease-in-out infinite;
|
||||
|
|
@ -1710,11 +1780,11 @@ defineExpose({
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 30px;
|
||||
height: 40px;
|
||||
width: 48px;
|
||||
height: 64px;
|
||||
background: linear-gradient(to bottom, #888 0%, #555 100%);
|
||||
border-radius: 10px 10px 0 0;
|
||||
border: 2px solid #333;
|
||||
border-radius: 16px 16px 0 0;
|
||||
border: 3px solid #333;
|
||||
}
|
||||
|
||||
.tombstone::before {
|
||||
|
|
@ -1723,7 +1793,7 @@ defineExpose({
|
|||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -1731,55 +1801,55 @@ defineExpose({
|
|||
/* Poop Sprite (Larger & More Detailed) */
|
||||
.poop {
|
||||
position: absolute;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.poop-sprite {
|
||||
position: relative;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
width: 4.8px;
|
||||
height: 4.8px;
|
||||
background: #3d2817;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow:
|
||||
/* Top point */
|
||||
0px -12px 0 #3d2817,
|
||||
0px -19.2px 0 #3d2817,
|
||||
/* Row 2 - narrow */
|
||||
-3px -9px 0 #3d2817, 0px -9px 0 #5a4028, 3px -9px 0 #3d2817,
|
||||
-4.8px -14.4px 0 #3d2817, 0px -14.4px 0 #5a4028, 4.8px -14.4px 0 #3d2817,
|
||||
/* Row 3 */
|
||||
-6px -6px 0 #3d2817, -3px -6px 0 #5a4028, 0px -6px 0 #6b4e38,
|
||||
3px -6px 0 #5a4028, 6px -6px 0 #3d2817,
|
||||
-9.6px -9.6px 0 #3d2817, -4.8px -9.6px 0 #5a4028, 0px -9.6px 0 #6b4e38,
|
||||
4.8px -9.6px 0 #5a4028, 9.6px -9.6px 0 #3d2817,
|
||||
/* Row 4 - eyes */
|
||||
-6px -3px 0 #3d2817, -3px -3px 0 #ffffff, 0px -3px 0 #6b4e38,
|
||||
3px -3px 0 #ffffff, 6px -3px 0 #3d2817,
|
||||
-9.6px -4.8px 0 #3d2817, -4.8px -4.8px 0 #ffffff, 0px -4.8px 0 #6b4e38,
|
||||
4.8px -4.8px 0 #ffffff, 9.6px -4.8px 0 #3d2817,
|
||||
/* Row 5 - middle */
|
||||
-9px 0px 0 #3d2817, -6px 0px 0 #5a4028, -3px 0px 0 #6b4e38, 0px 0px 0 #7d5a3a,
|
||||
3px 0px 0 #6b4e38, 6px 0px 0 #5a4028, 9px 0px 0 #3d2817,
|
||||
-14.4px 0px 0 #3d2817, -9.6px 0px 0 #5a4028, -4.8px 0px 0 #6b4e38, 0px 0px 0 #7d5a3a,
|
||||
4.8px 0px 0 #6b4e38, 9.6px 0px 0 #5a4028, 14.4px 0px 0 #3d2817,
|
||||
/* Row 6 */
|
||||
-9px 3px 0 #3d2817, -6px 3px 0 #5a4028, -3px 3px 0 #6b4e38, 0px 3px 0 #7d5a3a,
|
||||
3px 3px 0 #6b4e38, 6px 3px 0 #5a4028, 9px 3px 0 #3d2817,
|
||||
-14.4px 4.8px 0 #3d2817, -9.6px 4.8px 0 #5a4028, -4.8px 4.8px 0 #6b4e38, 0px 4.8px 0 #7d5a3a,
|
||||
4.8px 4.8px 0 #6b4e38, 9.6px 4.8px 0 #5a4028, 14.4px 4.8px 0 #3d2817,
|
||||
/* Row 7 */
|
||||
-12px 6px 0 #3d2817, -9px 6px 0 #3d2817, -6px 6px 0 #5a4028, -3px 6px 0 #6b4e38,
|
||||
0px 6px 0 #7d5a3a, 3px 6px 0 #6b4e38, 6px 6px 0 #5a4028, 9px 6px 0 #3d2817, 12px 6px 0 #3d2817,
|
||||
-19.2px 9.6px 0 #3d2817, -14.4px 9.6px 0 #3d2817, -9.6px 9.6px 0 #5a4028, -4.8px 9.6px 0 #6b4e38,
|
||||
0px 9.6px 0 #7d5a3a, 4.8px 9.6px 0 #6b4e38, 9.6px 9.6px 0 #5a4028, 14.4px 9.6px 0 #3d2817, 19.2px 9.6px 0 #3d2817,
|
||||
/* Row 8 - wider */
|
||||
-12px 9px 0 #3d2817, -9px 9px 0 #5a4028, -6px 9px 0 #6b4e38, -3px 9px 0 #7d5a3a,
|
||||
0px 9px 0 #7d5a3a, 3px 9px 0 #7d5a3a, 6px 9px 0 #6b4e38, 9px 9px 0 #5a4028, 12px 9px 0 #3d2817,
|
||||
-19.2px 14.4px 0 #3d2817, -14.4px 14.4px 0 #5a4028, -9.6px 14.4px 0 #6b4e38, -4.8px 14.4px 0 #7d5a3a,
|
||||
0px 14.4px 0 #7d5a3a, 4.8px 14.4px 0 #7d5a3a, 9.6px 14.4px 0 #6b4e38, 14.4px 14.4px 0 #5a4028, 19.2px 14.4px 0 #3d2817,
|
||||
/* Bottom row */
|
||||
-9px 12px 0 #3d2817, -6px 12px 0 #5a4028, -3px 12px 0 #6b4e38,
|
||||
0px 12px 0 #6b4e38, 3px 12px 0 #6b4e38, 6px 12px 0 #5a4028, 9px 12px 0 #3d2817;
|
||||
-14.4px 19.2px 0 #3d2817, -9.6px 19.2px 0 #5a4028, -4.8px 19.2px 0 #6b4e38,
|
||||
0px 19.2px 0 #6b4e38, 4.8px 19.2px 0 #6b4e38, 9.6px 19.2px 0 #5a4028, 14.4px 19.2px 0 #3d2817;
|
||||
}
|
||||
|
||||
/* Stink Animation (3 Fingers Style - Wavy) */
|
||||
.poop-stink {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
top: -40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
width: 64px;
|
||||
height: 48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
@ -1787,18 +1857,18 @@ defineExpose({
|
|||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background: transparent;
|
||||
/* Pixel art vertical wave pattern */
|
||||
box-shadow:
|
||||
0px 0px 0 #555,
|
||||
1px -2px 0 #555,
|
||||
1px -4px 0 #555,
|
||||
0px -6px 0 #555,
|
||||
-1px -8px 0 #555,
|
||||
-1px -10px 0 #555,
|
||||
0px -12px 0 #555;
|
||||
1.6px -3.2px 0 #555,
|
||||
1.6px -6.4px 0 #555,
|
||||
0px -9.6px 0 #555,
|
||||
-1.6px -12.8px 0 #555,
|
||||
-1.6px -16px 0 #555,
|
||||
0px -19.2px 0 #555;
|
||||
opacity: 0;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
|
@ -1810,45 +1880,32 @@ defineExpose({
|
|||
|
||||
/* Middle Finger */
|
||||
.stink-line.s2 {
|
||||
/* Slightly taller wave for middle */
|
||||
box-shadow:
|
||||
0px 0px 0 #555,
|
||||
1px -2px 0 #555,
|
||||
1px -4px 0 #555,
|
||||
0px -6px 0 #555,
|
||||
-1px -8px 0 #555,
|
||||
-1px -10px 0 #555,
|
||||
0px -12px 0 #555,
|
||||
1px -14px 0 #555;
|
||||
animation: stink-finger-2 2s ease-in-out infinite;
|
||||
animation-delay: 0.2s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
/* Right Finger */
|
||||
.stink-line.s3 {
|
||||
animation: stink-finger-3 2s ease-in-out infinite;
|
||||
animation-delay: 0.4s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
@keyframes stink-finger-1 {
|
||||
0% { opacity: 0; transform: translateX(-50%) rotate(-25deg) scaleY(0.5); }
|
||||
20% { opacity: 0.8; transform: translateX(-50%) rotate(-25deg) scaleY(1) translateY(-5px); }
|
||||
80% { opacity: 0.4; transform: translateX(-50%) rotate(-35deg) scaleY(1) translateY(-15px); }
|
||||
100% { opacity: 0; transform: translateX(-50%) rotate(-40deg) scaleY(1.2) translateY(-20px); }
|
||||
0% { opacity: 0; transform: translateX(-16px) scaleY(0.2); }
|
||||
50% { opacity: 0.6; transform: translateX(-24px) scaleY(1); }
|
||||
100% { opacity: 0; transform: translateX(-32px) scaleY(1.2); }
|
||||
}
|
||||
|
||||
@keyframes stink-finger-2 {
|
||||
0% { opacity: 0; transform: translateX(-50%) rotate(0deg) scaleY(0.5); }
|
||||
20% { opacity: 0.8; transform: translateX(-50%) rotate(0deg) scaleY(1) translateY(-6px); }
|
||||
80% { opacity: 0.4; transform: translateX(-50%) rotate(0deg) scaleY(1) translateY(-18px); }
|
||||
100% { opacity: 0; transform: translateX(-50%) rotate(0deg) scaleY(1.2) translateY(-24px); }
|
||||
0% { opacity: 0; transform: translateX(0) scaleY(0.2); }
|
||||
50% { opacity: 0.6; transform: translateX(0) scaleY(1.2); }
|
||||
100% { opacity: 0; transform: translateX(0) scaleY(1.4); }
|
||||
}
|
||||
|
||||
@keyframes stink-finger-3 {
|
||||
0% { opacity: 0; transform: translateX(-50%) rotate(25deg) scaleY(0.5); }
|
||||
20% { opacity: 0.8; transform: translateX(-50%) rotate(25deg) scaleY(1) translateY(-5px); }
|
||||
80% { opacity: 0.4; transform: translateX(-50%) rotate(35deg) scaleY(1) translateY(-15px); }
|
||||
100% { opacity: 0; transform: translateX(-50%) rotate(40deg) scaleY(1.2) translateY(-20px); }
|
||||
0% { opacity: 0; transform: translateX(16px) scaleY(0.2); }
|
||||
50% { opacity: 0.6; transform: translateX(24px) scaleY(1); }
|
||||
100% { opacity: 0; transform: translateX(32px) scaleY(1.2); }
|
||||
}
|
||||
|
||||
/* Poop Flush Animation */
|
||||
|
|
@ -1946,14 +2003,14 @@ defineExpose({
|
|||
}
|
||||
|
||||
.death-menu {
|
||||
margin-top: -30px; /* Overlay on top of tombstone */
|
||||
margin-top: -48px; /* Overlay on top of tombstone */
|
||||
background: rgba(224, 224, 224, 0.95);
|
||||
border: 4px solid #555;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 6px solid #555;
|
||||
padding: 16px;
|
||||
border-radius: 13px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 0 #333;
|
||||
max-width: 180px;
|
||||
box-shadow: 0 6px 0 #333;
|
||||
max-width: 288px;
|
||||
width: 90%;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
|
|
@ -1962,39 +2019,61 @@ defineExpose({
|
|||
|
||||
.death-title {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
font-size: 14px;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
.death-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
padding: 6px 10px;
|
||||
.death-btn {
|
||||
background: #fff;
|
||||
border: 2px solid #888;
|
||||
border-radius: 4px;
|
||||
border: 3px solid #333;
|
||||
padding: 13px;
|
||||
font-family: 'DotGothic16', monospace;
|
||||
font-size: 19px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.1s;
|
||||
white-space: normal;
|
||||
line-height: 1.2;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(2px);
|
||||
background: #ddd;
|
||||
.death-btn:hover {
|
||||
background: #eee;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 3px 0 #333;
|
||||
}
|
||||
|
||||
.death-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 0 0 #333;
|
||||
}
|
||||
|
||||
.death-btn.primary {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border-color: #2E7D32;
|
||||
}
|
||||
|
||||
.death-btn.primary:hover {
|
||||
background: #43A047;
|
||||
box-shadow: 0 3px 0 #1B5E20;
|
||||
}
|
||||
|
||||
.death-btn.secondary {
|
||||
background: #FFC107;
|
||||
color: #333;
|
||||
border-color: #FFA000;
|
||||
}
|
||||
|
||||
.death-btn.secondary:hover {
|
||||
background: #FFB300;
|
||||
box-shadow: 0 3px 0 #FF6F00;
|
||||
}
|
||||
|
||||
/* Mood Animations */
|
||||
.pet-root.mood-happy {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
<template>
|
||||
<div class="top-menu">
|
||||
<button class="icon-btn icon-stats" @click="$emit('info')" title="Status"></button>
|
||||
<button class="icon-btn icon-feed" @click="$emit('feed')" :disabled="disabled" title="Feed"></button>
|
||||
<button class="icon-btn icon-play" @click="$emit('playMenu')" :disabled="disabled" title="Play"></button>
|
||||
<button class="icon-btn icon-feed" @click="$emit('feed')" :disabled="disabled || isSleeping" title="Feed"></button>
|
||||
<!-- <button class="icon-btn icon-play" @click="$emit('playMenu')" :disabled="disabled || isSleeping" title="Play"></button> -->
|
||||
<button class="icon-btn" @click="$emit('playMenu')" :disabled="disabled || isSleeping" title="Play">
|
||||
<IconGamepad />
|
||||
</button>
|
||||
<button class="icon-btn icon-temple" @click="$emit('temple')" :disabled="disabled" title="Temple"></button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import IconGamepad from './icon/game.vue'
|
||||
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isSleeping: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -23,14 +32,14 @@ defineEmits(['info', 'feed', 'playMenu', 'temple']);
|
|||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
padding: 8px 16px;
|
||||
background: rgba(155, 188, 15, 0.05);
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 3px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
|
@ -43,79 +52,205 @@ defineEmits(['info', 'feed', 'playMenu', 'temple']);
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Stats Icon (Bar Chart) */
|
||||
/* 共用:2px 為一個 pixel 單位 */
|
||||
.icon-btn::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* ===================== Stats:精緻眼鏡 ===================== */
|
||||
.icon-stats::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #333;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
-6px 4px 0 #333, -6px 2px 0 #333, -6px 0px 0 #333,
|
||||
-2px 4px 0 #333, -2px 2px 0 #333, -2px 0px 0 #333, -2px -2px 0 #333,
|
||||
2px 4px 0 #333, 2px 2px 0 #333, 2px 0px 0 #333, 2px -2px 0 #333, 2px -4px 0 #333,
|
||||
6px 4px 0 #333, 6px 2px 0 #333;
|
||||
/* 左框外框 */
|
||||
-10px -6px 0 #000, -8px -6px 0 #000, -6px -6px 0 #000,
|
||||
-12px -4px 0 #000, -12px -2px 0 #000, -12px 0px 0 #000, -12px 2px 0 #000,
|
||||
-10px 4px 0 #000, -8px 4px 0 #000, -6px 4px 0 #000,
|
||||
-4px -4px 0 #000, -4px -2px 0 #000, -4px 0px 0 #000, -4px 2px 0 #000,
|
||||
|
||||
/* 左鏡片 */
|
||||
-10px -4px 0 #b3d9ff, -8px -4px 0 #b3d9ff, -6px -4px 0 #b3d9ff,
|
||||
-10px -2px 0 #b3d9ff, -8px -2px 0 #e3f2ff, -6px -2px 0 #b3d9ff,
|
||||
-10px 0px 0 #b3d9ff, -8px 0px 0 #b3d9ff, -6px 0px 0 #b3d9ff,
|
||||
|
||||
/* 右框外框 */
|
||||
6px -6px 0 #000, 8px -6px 0 #000, 10px -6px 0 #000,
|
||||
4px -4px 0 #000, 4px -2px 0 #000, 4px 0px 0 #000, 4px 2px 0 #000,
|
||||
6px 4px 0 #000, 8px 4px 0 #000, 10px 4px 0 #000,
|
||||
12px -4px 0 #000, 12px -2px 0 #000, 12px 0px 0 #000, 12px 2px 0 #000,
|
||||
|
||||
/* 右鏡片 */
|
||||
6px -4px 0 #b3d9ff, 8px -4px 0 #b3d9ff, 10px -4px 0 #b3d9ff,
|
||||
6px -2px 0 #b3d9ff, 8px -2px 0 #e3f2ff, 10px -2px 0 #b3d9ff,
|
||||
6px 0px 0 #b3d9ff, 8px 0px 0 #b3d9ff, 10px 0px 0 #b3d9ff,
|
||||
|
||||
/* 鼻橋 */
|
||||
-4px -2px 0 #000, -2px -2px 0 #000, 0px -2px 0 #000, 2px -2px 0 #000,
|
||||
|
||||
/* 鏡腳(向左右平行伸出) */
|
||||
-14px -4px 0 #000, -16px -4px 0 #000,
|
||||
14px -4px 0 #000, 16px -4px 0 #000;
|
||||
}
|
||||
|
||||
/* Feed Icon (Apple/Food) */
|
||||
/* ===================== Feed:精緻壽司 ===================== */
|
||||
.icon-feed::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #ff4444;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
-2px -6px 0 #228822,
|
||||
-4px -2px 0 #ff4444, -2px -2px 0 #ff4444, 0px -2px 0 #ff4444, 2px -2px 0 #ff4444,
|
||||
-4px 0px 0 #ff4444, -2px 0px 0 #ff4444, 0px 0px 0 #ff4444, 2px 0px 0 #ff4444, 4px 0px 0 #ff4444,
|
||||
-4px 2px 0 #ff4444, -2px 2px 0 #ff4444, 0px 2px 0 #ff4444, 2px 2px 0 #ff4444, 4px 2px 0 #ff4444,
|
||||
-2px 4px 0 #ff4444, 0px 4px 0 #ff4444, 2px 4px 0 #ff4444;
|
||||
/* 海苔外框 */
|
||||
-12px -4px 0 #000, -10px -4px 0 #000, -8px -4px 0 #000, -6px -4px 0 #000,
|
||||
-4px -4px 0 #000, -2px -4px 0 #000, 0px -4px 0 #000, 2px -4px 0 #000,
|
||||
4px -4px 0 #000, 6px -4px 0 #000, 8px -4px 0 #000, 10px -4px 0 #000,
|
||||
-12px -2px 0 #000, 10px -2px 0 #000,
|
||||
-12px 0px 0 #000, 10px 0px 0 #000,
|
||||
-12px 2px 0 #000, 10px 2px 0 #000,
|
||||
-12px 4px 0 #000, -10px 4px 0 #000, -8px 4px 0 #000, -6px 4px 0 #000,
|
||||
-4px 4px 0 #000, -2px 4px 0 #000, 0px 4px 0 #000, 2px 4px 0 #000,
|
||||
4px 4px 0 #000, 6px 4px 0 #000, 8px 4px 0 #000, 10px 4px 0 #000,
|
||||
|
||||
/* 白飯 */
|
||||
-10px -2px 0 #fafafa, -8px -2px 0 #ffffff, -6px -2px 0 #fafafa,
|
||||
-4px -2px 0 #ffffff, -2px -2px 0 #fafafa, 0px -2px 0 #ffffff,
|
||||
2px -2px 0 #fafafa, 4px -2px 0 #ffffff, 6px -2px 0 #fafafa, 8px -2px 0 #ffffff,
|
||||
|
||||
-10px 0px 0 #ffffff, -8px 0px 0 #fafafa, -6px 0px 0 #ffffff,
|
||||
-4px 0px 0 #fafafa, -2px 0px 0 #ffffff, 0px 0px 0 #fafafa,
|
||||
2px 0px 0 #ffffff, 4px 0px 0 #fafafa, 6px 0px 0 #ffffff, 8px 0px 0 #fafafa,
|
||||
|
||||
-10px 2px 0 #fafafa, -8px 2px 0 #ffffff, -6px 2px 0 #fafafa,
|
||||
-4px 2px 0 #ffffff, -2px 2px 0 #fafafa, 0px 2px 0 #ffffff,
|
||||
2px 2px 0 #fafafa, 4px 2px 0 #ffffff, 6px 2px 0 #fafafa, 8px 2px 0 #ffffff,
|
||||
|
||||
/* 鮭魚餡料 */
|
||||
-2px 0px 0 #ff7043, 0px 0px 0 #ff8a65, 2px 0px 0 #ff7043;
|
||||
}
|
||||
|
||||
/* Play Icon (Ball/Game) */
|
||||
/* Play Icon:灰色像素手把(依原圖重製,十字鍵加粗) */
|
||||
.icon-play::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #4444ff;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
-2px -4px 0 #4444ff, 0px -4px 0 #4444ff, 2px -4px 0 #4444ff,
|
||||
-4px -2px 0 #4444ff, -2px -2px 0 #4444ff, 0px -2px 0 #4444ff, 2px -2px 0 #4444ff, 4px -2px 0 #4444ff,
|
||||
-4px 0px 0 #4444ff, -2px 0px 0 #4444ff, 0px 0px 0 #4444ff, 2px 0px 0 #4444ff, 4px 0px 0 #4444ff,
|
||||
-4px 2px 0 #4444ff, -2px 2px 0 #4444ff, 0px 2px 0 #4444ff, 2px 2px 0 #4444ff, 4px 2px 0 #4444ff,
|
||||
-2px 4px 0 #4444ff, 0px 4px 0 #4444ff, 2px 4px 0 #4444ff;
|
||||
/* ===== 線材(上方那一根線) ===== */
|
||||
0px -12px 0 #000,
|
||||
0px -10px 0 #000,
|
||||
|
||||
/* ===== 外框:上緣 ===== */
|
||||
-10px -8px 0 #000, -8px -8px 0 #000, -6px -8px 0 #000,
|
||||
-4px -8px 0 #000, -2px -8px 0 #000, 0px -8px 0 #000,
|
||||
2px -8px 0 #000, 4px -8px 0 #000, 6px -8px 0 #000,
|
||||
8px -8px 0 #000, 10px -8px 0 #000,
|
||||
|
||||
/* 外框:第二層(稍微圓角) */
|
||||
-12px -6px 0 #000, 10px -6px 0 #000,
|
||||
|
||||
/* 外框:側邊 */
|
||||
-12px -4px 0 #000, 12px -4px 0 #000,
|
||||
-12px -2px 0 #000, 12px -2px 0 #000,
|
||||
-10px 0px 0 #000, 10px 0px 0 #000,
|
||||
-8px 2px 0 #000, 8px 2px 0 #000,
|
||||
|
||||
/* 外框:底部弧形 */
|
||||
-6px 4px 0 #000, -4px 4px 0 #000, -2px 4px 0 #000,
|
||||
0px 4px 0 #000, 2px 4px 0 #000, 4px 4px 0 #000,
|
||||
6px 4px 0 #000,
|
||||
|
||||
/* ===== 機身填色(灰色) ===== */
|
||||
-10px -6px 0 #bfbfbf, -8px -6px 0 #cfcfcf, -6px -6px 0 #d8d8d8,
|
||||
-4px -6px 0 #d8d8d8, -2px -6px 0 #d8d8d8, 0px -6px 0 #d8d8d8,
|
||||
2px -6px 0 #d8d8d8, 4px -6px 0 #d8d8d8, 6px -6px 0 #cfcfcf,
|
||||
8px -6px 0 #bfbfbf,
|
||||
|
||||
-10px -4px 0 #cfcfcf, -8px -4px 0 #d8d8d8, -6px -4px 0 #e4e4e4,
|
||||
-4px -4px 0 #e4e4e4, -2px -4px 0 #e4e4e4, 0px -4px 0 #e4e4e4,
|
||||
2px -4px 0 #e4e4e4, 4px -4px 0 #e4e4e4, 6px -4px 0 #d8d8d8,
|
||||
8px -4px 0 #cfcfcf,
|
||||
|
||||
-10px -2px 0 #cfcfcf, -8px -2px 0 #d8d8d8, -6px -2px 0 #e4e4e4,
|
||||
-4px -2px 0 #e4e4e4, -2px -2px 0 #e4e4e4, 0px -2px 0 #e4e4e4,
|
||||
2px -2px 0 #e4e4e4, 4px -2px 0 #e4e4e4, 6px -2px 0 #d8d8d8,
|
||||
8px -2px 0 #cfcfcf,
|
||||
|
||||
-8px 0px 0 #cfcfcf, -6px 0px 0 #d8d8d8, -4px 0px 0 #d8d8d8,
|
||||
-2px 0px 0 #d8d8d8, 0px 0px 0 #d8d8d8, 2px 0px 0 #d8d8d8,
|
||||
4px 0px 0 #d8d8d8, 6px 0px 0 #d8d8d8, 8px 0px 0 #cfcfcf,
|
||||
|
||||
/* ===== 左十字鍵(加粗、清楚) ===== */
|
||||
/* 中心 */
|
||||
-6px -2px 0 #000,
|
||||
/* 上下 */
|
||||
-6px -4px 0 #000, -6px 0px 0 #000,
|
||||
/* 左右 */
|
||||
-8px -2px 0 #000, -4px -2px 0 #000,
|
||||
/* 角落補點,讓形狀更「方」 */
|
||||
-8px -4px 0 #000, -4px -4px 0 #000,
|
||||
-8px 0px 0 #000, -4px 0px 0 #000,
|
||||
|
||||
/* ===== 右十字鍵(加粗、清楚) ===== */
|
||||
/* 中心 */
|
||||
6px -2px 0 #000,
|
||||
/* 上下 */
|
||||
6px -4px 0 #000, 6px 0px 0 #000,
|
||||
/* 左右 */
|
||||
4px -2px 0 #000, 8px -2px 0 #000,
|
||||
/* 角落補點 */
|
||||
4px -4px 0 #000, 8px -4px 0 #000,
|
||||
4px 0px 0 #000, 8px 0px 0 #000,
|
||||
|
||||
/* ===== 嘴巴(中間那條小橫線) ===== */
|
||||
-2px 2px 0 #000, 0px 2px 0 #000, 2px 2px 0 #000;
|
||||
}
|
||||
|
||||
/* Temple Icon (廟宇) */
|
||||
/* ===================== Temple:精細小廟 ===================== */
|
||||
.icon-temple::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #D2691E;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: transparent;
|
||||
box-shadow:
|
||||
/* 屋頂 */
|
||||
-4px -6px 0 #8B4513, -2px -6px 0 #8B4513, 0px -6px 0 #8B4513, 2px -6px 0 #8B4513, 4px -6px 0 #8B4513,
|
||||
/* 屋簷 */
|
||||
-6px -4px 0 #D2691E, -4px -4px 0 #D2691E, -2px -4px 0 #D2691E, 0px -4px 0 #D2691E, 2px -4px 0 #D2691E, 4px -4px 0 #D2691E, 6px -4px 0 #D2691E,
|
||||
/* 柱子與牆 */
|
||||
-4px -2px 0 #8B4513, 4px -2px 0 #8B4513,
|
||||
-4px 0px 0 #8B4513, -2px 0px 0 #FFD700, 0px 0px 0 #FFD700, 2px 0px 0 #FFD700, 4px 0px 0 #8B4513,
|
||||
-4px 2px 0 #8B4513, 4px 2px 0 #8B4513,
|
||||
/* 屋頂尖端 */
|
||||
-2px -14px 0 #4e342e, 0px -14px 0 #4e342e, 2px -14px 0 #4e342e,
|
||||
|
||||
/* 屋頂主體 */
|
||||
-10px -12px 0 #6d4c41, -8px -12px 0 #6d4c41, -6px -12px 0 #6d4c41,
|
||||
-4px -12px 0 #6d4c41, -2px -12px 0 #6d4c41, 0px -12px 0 #6d4c41,
|
||||
2px -12px 0 #6d4c41, 4px -12px 0 #6d4c41, 6px -12px 0 #6d4c41,
|
||||
8px -12px 0 #6d4c41, 10px -12px 0 #6d4c41,
|
||||
|
||||
/* 屋簷金邊 */
|
||||
-12px -10px 0 #d7a35f, -10px -10px 0 #d7a35f, -8px -10px 0 #d7a35f,
|
||||
-6px -10px 0 #d7a35f, -4px -10px 0 #d7a35f, -2px -10px 0 #d7a35f,
|
||||
0px -10px 0 #d7a35f, 2px -10px 0 #d7a35f, 4px -10px 0 #d7a35f,
|
||||
6px -10px 0 #d7a35f, 8px -10px 0 #d7a35f, 10px -10px 0 #d7a35f, 12px -10px 0 #d7a35f,
|
||||
|
||||
/* 柱子 */
|
||||
-10px -8px 0 #4e342e, -10px -6px 0 #4e342e, -10px -4px 0 #4e342e,
|
||||
10px -8px 0 #4e342e, 10px -6px 0 #4e342e, 10px -4px 0 #4e342e,
|
||||
|
||||
/* 牆體 */
|
||||
-8px -8px 0 #7b5e57, -6px -8px 0 #7b5e57, -4px -8px 0 #7b5e57,
|
||||
-2px -8px 0 #7b5e57, 0px -8px 0 #7b5e57, 2px -8px 0 #7b5e57,
|
||||
4px -8px 0 #7b5e57, 6px -8px 0 #7b5e57, 8px -8px 0 #7b5e57,
|
||||
|
||||
/* 中央金色神龕 */
|
||||
-6px -6px 0 #ffca28, -4px -6px 0 #ffd54f, -2px -6px 0 #ffd54f,
|
||||
0px -6px 0 #ffd54f, 2px -6px 0 #ffd54f, 4px -6px 0 #ffca28,
|
||||
|
||||
-6px -4px 0 #ffd54f, -4px -4px 0 #fff176, -2px -4px 0 #fff9c4,
|
||||
0px -4px 0 #fff9c4, 2px -4px 0 #fff176, 4px -4px 0 #ffd54f,
|
||||
|
||||
-6px -2px 0 #ffca28, -4px -2px 0 #ffd54f, -2px -2px 0 #ffd54f,
|
||||
0px -2px 0 #ffd54f, 2px -2px 0 #ffd54f, 4px -2px 0 #ffca28,
|
||||
|
||||
/* 底座 */
|
||||
-4px 4px 0 #654321, -2px 4px 0 #654321, 0px 4px 0 #654321, 2px 4px 0 #654321, 4px 4px 0 #654321;
|
||||
-10px 0px 0 #4e342e, -8px 0px 0 #4e342e, -6px 0px 0 #4e342e,
|
||||
-4px 0px 0 #4e342e, -2px 0px 0 #4e342e, 0px 0px 0 #4e342e,
|
||||
2px 0px 0 #4e342e, 4px 0px 0 #4e342e, 6px 0px 0 #4e342e, 8px 0px 0 #4e342e, 10px 0px 0 #4e342e;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<!-- components/IconGamepad.vue -->
|
||||
<template>
|
||||
<svg
|
||||
viewBox="0 0 32 20"
|
||||
width="20"
|
||||
height="20"
|
||||
shape-rendering="crispEdges"
|
||||
>
|
||||
<!-- 線材 -->
|
||||
<rect x="15" y="0" width="2" height="4" fill="#000" />
|
||||
|
||||
<!-- 手把本體 -->
|
||||
<rect
|
||||
x="2"
|
||||
y="6"
|
||||
width="28"
|
||||
height="10"
|
||||
rx="5"
|
||||
ry="5"
|
||||
fill="#d8d8d8"
|
||||
stroke="#000"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
<!-- 左側稍微陰影,做一點立體感 -->
|
||||
<rect x="3" y="7" width="4" height="8" fill="#bfbfbf" opacity="0.9" />
|
||||
|
||||
<!-- 右側稍微陰影 -->
|
||||
<rect x="25" y="7" width="4" height="8" fill="#bfbfbf" opacity="0.9" />
|
||||
|
||||
<!-- 左邊十字鍵 -->
|
||||
<rect x="8" y="9" width="2" height="2" fill="#000" />
|
||||
<rect x="8" y="7" width="2" height="2" fill="#000" />
|
||||
<rect x="8" y="11" width="2" height="2" fill="#000" />
|
||||
<rect x="6" y="9" width="2" height="2" fill="#000" />
|
||||
<rect x="10" y="9" width="2" height="2" fill="#000" />
|
||||
|
||||
<!-- 右邊十字鍵 -->
|
||||
<rect x="22" y="9" width="2" height="2" fill="#000" />
|
||||
<rect x="22" y="7" width="2" height="2" fill="#000" />
|
||||
<rect x="22" y="11" width="2" height="2" fill="#000" />
|
||||
<rect x="20" y="9" width="2" height="2" fill="#000" />
|
||||
<rect x="24" y="9" width="2" height="2" fill="#000" />
|
||||
|
||||
<!-- 嘴巴(中間小橫線) -->
|
||||
<rect x="14" y="11" width="4" height="2" fill="#000" />
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -1,144 +1,135 @@
|
|||
import { ref } from 'vue';
|
||||
import { api } from '../services/api';
|
||||
|
||||
export function useEventSystem(petSystem) {
|
||||
const { stats, state, triggerState } = petSystem;
|
||||
|
||||
const currentEvent = ref(null);
|
||||
const eventHistory = ref([]);
|
||||
const eventPool = ref([]);
|
||||
const eventChance = ref(0.01);
|
||||
|
||||
// --- Event Database ---
|
||||
// Categories: 'good', 'bad', 'weird'
|
||||
const EVENT_DATABASE = [
|
||||
// --- Good Events ---
|
||||
{
|
||||
id: 'found_charm',
|
||||
type: 'good',
|
||||
text: '撿到小符',
|
||||
effect: (s) => { s.happiness = Math.min(100, s.happiness + 5); },
|
||||
condition: () => true,
|
||||
icon: 'pixel-charm'
|
||||
},
|
||||
{
|
||||
id: 'self_clean',
|
||||
type: 'good',
|
||||
text: '自己掃地',
|
||||
effect: (s) => { if (s.poopCount > 0) s.poopCount--; },
|
||||
// --- Event Logic Map ---
|
||||
// Maps JSON event IDs to complex conditions or custom effects if needed
|
||||
// Simple effects (stat changes) are handled automatically
|
||||
const LOGIC_MAP = {
|
||||
'self_clean': {
|
||||
condition: (s) => s.poopCount > 0,
|
||||
icon: 'pixel-broom'
|
||||
customEffect: (s) => { if (s.poopCount > 0) s.poopCount--; }
|
||||
},
|
||||
{
|
||||
id: 'dance',
|
||||
type: 'good',
|
||||
text: '開心地跳舞',
|
||||
effect: (s) => { s.happiness = Math.min(100, s.happiness + 10); },
|
||||
condition: (s) => s.happiness > 70,
|
||||
icon: 'pixel-note'
|
||||
'dance': {
|
||||
condition: (s) => s.happiness > 70
|
||||
},
|
||||
{
|
||||
id: 'gift_apple',
|
||||
type: 'good',
|
||||
text: '給你一顆果子',
|
||||
effect: (s) => { /* Add to inventory logic later */ s.hunger = Math.min(100, s.hunger + 5); },
|
||||
condition: () => Math.random() < 0.3,
|
||||
icon: 'pixel-apple'
|
||||
'night_noise': {
|
||||
condition: () => isNight()
|
||||
},
|
||||
|
||||
// --- Bad Events ---
|
||||
{
|
||||
id: 'night_noise',
|
||||
type: 'bad',
|
||||
text: '半夜亂叫',
|
||||
effect: (s) => { s.happiness = Math.max(0, s.happiness - 5); },
|
||||
condition: (s) => isNight(),
|
||||
icon: 'pixel-angry'
|
||||
'overeat': {
|
||||
condition: (s) => s.hunger > 80
|
||||
},
|
||||
{
|
||||
id: 'overeat',
|
||||
type: 'bad',
|
||||
text: '偷吃太多',
|
||||
effect: (s) => { s.hunger = Math.min(100, s.hunger + 20); s.health = Math.max(0, s.health - 5); },
|
||||
condition: (s) => s.hunger > 80,
|
||||
icon: 'pixel-meat'
|
||||
'stomach_ache': {
|
||||
condition: (s) => s.health < 50
|
||||
},
|
||||
{
|
||||
id: 'stomach_ache',
|
||||
type: 'bad',
|
||||
text: '胃痛',
|
||||
effect: (s) => { s.health = Math.max(0, s.health - 10); },
|
||||
condition: (s) => s.health < 50,
|
||||
icon: 'pixel-skull'
|
||||
},
|
||||
|
||||
// --- Weird Events ---
|
||||
{
|
||||
id: 'stare',
|
||||
type: 'weird',
|
||||
text: '盯著螢幕外面看...',
|
||||
effect: () => { },
|
||||
condition: () => true,
|
||||
icon: 'pixel-eye'
|
||||
},
|
||||
{
|
||||
id: 'corner_squat',
|
||||
type: 'weird',
|
||||
text: '走到角落蹲著',
|
||||
effect: (s) => { s.happiness = Math.max(0, s.happiness - 5); },
|
||||
condition: () => true,
|
||||
icon: 'pixel-cloud'
|
||||
},
|
||||
{
|
||||
id: 'glitch',
|
||||
type: 'weird',
|
||||
text: '#@!$%^&*',
|
||||
effect: (s) => { if (s.int > 5) s.int++; },
|
||||
'glitch': {
|
||||
condition: () => Math.random() < 0.1,
|
||||
icon: 'pixel-glitch'
|
||||
customEffect: (s) => { if (s.int > 5) s.int++; }
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
function isNight() {
|
||||
const hour = new Date().getHours();
|
||||
return hour >= 22 || hour < 6;
|
||||
}
|
||||
|
||||
async function loadEventConfig() {
|
||||
try {
|
||||
const config = await api.getGameConfig();
|
||||
if (config && config.events) {
|
||||
eventPool.value = config.events.eventPool || [];
|
||||
eventChance.value = config.events.randomEventChance || 0.01;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load event config", e);
|
||||
}
|
||||
}
|
||||
|
||||
function checkEventTriggers() {
|
||||
if (state.value === 'sleep' || state.value === 'dead') return;
|
||||
|
||||
// 10% chance to trigger an event per check
|
||||
// Destiny Effect: Luck (福運) - Good events more likely?
|
||||
// Destiny Effect: Spiritual (靈視) - Night events more likely
|
||||
// Reload config occasionally? For now assume loaded on init.
|
||||
if (eventPool.value.length === 0) {
|
||||
loadEventConfig(); // Try to load if empty
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
if (Math.random() < eventChance.value) {
|
||||
triggerRandomEvent();
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRandomEvent() {
|
||||
// Filter valid events
|
||||
const validEvents = EVENT_DATABASE.filter(e => e.condition(stats.value));
|
||||
const validEvents = eventPool.value.filter(event => {
|
||||
const logic = LOGIC_MAP[event.id];
|
||||
if (logic && logic.condition) {
|
||||
return logic.condition(stats.value);
|
||||
}
|
||||
return true; // Default to true if no condition
|
||||
});
|
||||
|
||||
if (validEvents.length === 0) return;
|
||||
|
||||
const event = validEvents[Math.floor(Math.random() * validEvents.length)];
|
||||
// Weighted Random Selection
|
||||
const totalWeight = validEvents.reduce((sum, e) => sum + (e.weight || 1), 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
let selectedEvent = validEvents[0];
|
||||
|
||||
// Apply effect
|
||||
event.effect(stats.value);
|
||||
for (const event of validEvents) {
|
||||
random -= (event.weight || 1);
|
||||
if (random <= 0) {
|
||||
selectedEvent = event;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
applyEventEffect(selectedEvent);
|
||||
|
||||
// Set current event for UI
|
||||
currentEvent.value = event;
|
||||
eventHistory.value.unshift({ ...event, time: new Date() });
|
||||
currentEvent.value = selectedEvent;
|
||||
eventHistory.value.unshift({ ...selectedEvent, time: new Date() });
|
||||
|
||||
// Auto-clear event after 3 seconds
|
||||
// Auto-clear event after 4 seconds
|
||||
setTimeout(() => {
|
||||
currentEvent.value = null;
|
||||
}, 4000);
|
||||
|
||||
console.log('Event Triggered:', event.text);
|
||||
console.log('Event Triggered:', selectedEvent.text);
|
||||
}
|
||||
|
||||
function applyEventEffect(event) {
|
||||
// 1. Apply simple stat changes from JSON
|
||||
if (event.effect) {
|
||||
for (const key in event.effect) {
|
||||
if (key in stats.value) {
|
||||
stats.value[key] = Math.max(0, Math.min(100, stats.value[key] + event.effect[key]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Apply custom logic
|
||||
const logic = LOGIC_MAP[event.id];
|
||||
if (logic && logic.customEffect) {
|
||||
logic.customEffect(stats.value);
|
||||
}
|
||||
|
||||
// Sync changes
|
||||
api.updatePetStatus({ stats: stats.value });
|
||||
}
|
||||
|
||||
return {
|
||||
currentEvent,
|
||||
eventHistory,
|
||||
checkEventTriggers,
|
||||
triggerRandomEvent
|
||||
triggerRandomEvent,
|
||||
loadEventConfig
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { api } from '../services/api';
|
||||
|
||||
export function usePetSystem() {
|
||||
// --- State ---
|
||||
const stage = ref('egg'); // egg, baby, adult
|
||||
const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse
|
||||
const isLoading = ref(true);
|
||||
const error = ref(null);
|
||||
|
||||
// --- Destiny Data ---
|
||||
const DESTINIES = [
|
||||
|
|
@ -43,6 +46,16 @@ export function usePetSystem() {
|
|||
dailyPrayerCount: 0
|
||||
});
|
||||
|
||||
// Game Config (loaded from API)
|
||||
const config = ref({
|
||||
rates: {
|
||||
hungerDecay: 0.05,
|
||||
happinessDecay: 0.08,
|
||||
poopChance: 0.005,
|
||||
sickChance: 0.1
|
||||
}
|
||||
});
|
||||
|
||||
const achievements = ref([
|
||||
{ id: 'newbie', name: '新手飼主', desc: '養育超過 1 天', unlocked: false, icon: '🥚' },
|
||||
{ id: 'veteran', name: '資深飼主', desc: '養育超過 7 天', unlocked: false, icon: '🏆' },
|
||||
|
|
@ -58,6 +71,48 @@ export function usePetSystem() {
|
|||
|
||||
const isCleaning = ref(false);
|
||||
|
||||
// --- Initialization ---
|
||||
async function initGame() {
|
||||
try {
|
||||
console.log('🎮 initGame: Starting...');
|
||||
isLoading.value = true;
|
||||
|
||||
// Load Pet Data
|
||||
console.log('🎮 initGame: Calling api.getPetStatus()...');
|
||||
const petData = await api.getPetStatus();
|
||||
console.log('🎮 initGame: Got petData:', petData);
|
||||
|
||||
stage.value = petData.stage;
|
||||
state.value = petData.state;
|
||||
stats.value = { ...stats.value, ...petData.stats }; // Merge defaults
|
||||
|
||||
// Load Config
|
||||
console.log('🎮 initGame: Calling api.getGameConfig()...');
|
||||
const configData = await api.getGameConfig();
|
||||
console.log('🎮 initGame: Got configData:', configData);
|
||||
|
||||
if (configData) {
|
||||
config.value = configData;
|
||||
}
|
||||
|
||||
console.log('🎮 initGame: Success! Setting isLoading to false');
|
||||
isLoading.value = false;
|
||||
console.log('🎮 initGame: isLoading.value is now:', isLoading.value);
|
||||
console.log('🎮 initGame: typeof isLoading:', typeof isLoading);
|
||||
console.log('🎮 initGame: isLoading object:', isLoading);
|
||||
startGameLoop();
|
||||
} catch (err) {
|
||||
console.error("❌ Failed to init game:", err);
|
||||
error.value = "Failed to load game data.";
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startGameLoop() {
|
||||
if (gameLoopId) clearInterval(gameLoopId);
|
||||
gameLoopId = setInterval(tick, TICK_RATE);
|
||||
}
|
||||
|
||||
// --- Actions ---
|
||||
|
||||
function assignDestiny() {
|
||||
|
|
@ -76,9 +131,12 @@ export function usePetSystem() {
|
|||
const picked = pool[Math.floor(Math.random() * pool.length)];
|
||||
stats.value.destiny = picked;
|
||||
console.log('Assigned Destiny:', picked);
|
||||
|
||||
// Sync to API
|
||||
api.updatePetStatus({ stats: { destiny: picked } });
|
||||
}
|
||||
|
||||
function feed() {
|
||||
async function feed() {
|
||||
if (state.value === 'sleep' || state.value === 'dead' || stage.value === 'egg' || isCleaning.value) return false;
|
||||
|
||||
if (state.value === 'sick' || stats.value.hunger >= 90) {
|
||||
|
|
@ -92,12 +150,21 @@ export function usePetSystem() {
|
|||
stats.value.hunger = Math.min(100, stats.value.hunger + 20);
|
||||
stats.value.weight += 50;
|
||||
|
||||
// Chance to poop after eating (降低機率)
|
||||
// Destiny Effect: Gluttony (暴食) might increase poop chance? Or just hunger decay.
|
||||
if (Math.random() < 0.15) { // 從 0.3 降到 0.15
|
||||
setTimeout(() => {
|
||||
// Sync to API
|
||||
await api.updatePetStatus({
|
||||
state: 'eating',
|
||||
stats: {
|
||||
hunger: stats.value.hunger,
|
||||
weight: stats.value.weight
|
||||
}
|
||||
});
|
||||
|
||||
// Chance to poop after eating
|
||||
if (Math.random() < 0.15) {
|
||||
setTimeout(async () => {
|
||||
if (stats.value.poopCount < 4) {
|
||||
stats.value.poopCount++;
|
||||
await api.updatePetStatus({ stats: { poopCount: stats.value.poopCount } });
|
||||
}
|
||||
}, 4000);
|
||||
}
|
||||
|
|
@ -105,24 +172,40 @@ export function usePetSystem() {
|
|||
return true;
|
||||
}
|
||||
|
||||
function play() {
|
||||
async function play() {
|
||||
if (state.value !== 'idle' || stage.value === 'egg' || isCleaning.value) return false;
|
||||
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 15);
|
||||
stats.value.weight -= 10; // Exercise burns calories
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - 5);
|
||||
|
||||
await api.updatePetStatus({
|
||||
stats: {
|
||||
happiness: stats.value.happiness,
|
||||
weight: stats.value.weight,
|
||||
hunger: stats.value.hunger
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function clean() {
|
||||
async function clean() {
|
||||
if (stats.value.poopCount > 0 && !isCleaning.value) {
|
||||
isCleaning.value = true;
|
||||
|
||||
// Delay removal for animation
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
stats.value.poopCount = 0;
|
||||
stats.value.happiness += 10;
|
||||
isCleaning.value = false;
|
||||
|
||||
await api.updatePetStatus({
|
||||
stats: {
|
||||
poopCount: 0,
|
||||
happiness: stats.value.happiness
|
||||
}
|
||||
});
|
||||
}, 2000); // 2 seconds flush animation
|
||||
|
||||
return true;
|
||||
|
|
@ -130,7 +213,7 @@ export function usePetSystem() {
|
|||
return false;
|
||||
}
|
||||
|
||||
function sleep() {
|
||||
async function sleep() {
|
||||
if (isCleaning.value) return;
|
||||
|
||||
if (state.value === 'idle') {
|
||||
|
|
@ -138,27 +221,34 @@ export function usePetSystem() {
|
|||
} else if (state.value === 'sleep') {
|
||||
state.value = 'idle'; // Wake up
|
||||
}
|
||||
|
||||
await api.updatePetStatus({ state: state.value });
|
||||
}
|
||||
|
||||
// --- Game Loop ---
|
||||
function tick() {
|
||||
if (state.value === 'dead' || stage.value === 'egg') return;
|
||||
|
||||
// Use rates from config
|
||||
const rates = config.value.rates || {
|
||||
hungerDecay: 0.05,
|
||||
happinessDecay: 0.08,
|
||||
poopChance: 0.005,
|
||||
sickChance: 0.1
|
||||
};
|
||||
|
||||
// Decrease stats naturally
|
||||
// Destiny Effect: Gluttony (暴食) - Hunger decreases faster (+30%)
|
||||
let hungerDecay = 0.05;
|
||||
let hungerDecay = rates.hungerDecay;
|
||||
if (stats.value.destiny?.id === 'gluttony') {
|
||||
hungerDecay *= 1.3;
|
||||
}
|
||||
|
||||
// Destiny Effect: Playful (愛玩) - Happiness decreases faster
|
||||
let happinessDecay = 0.08;
|
||||
let happinessDecay = rates.happinessDecay;
|
||||
if (stats.value.destiny?.id === 'playful') {
|
||||
happinessDecay *= 1.2; // Faster decay
|
||||
}
|
||||
|
||||
// Destiny Effect: DEX (敏捷) - Hunger decreases slower
|
||||
// DEX 10 = -10% decay, DEX 50 = -50% decay
|
||||
if (stats.value.dex > 0) {
|
||||
const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction
|
||||
hungerDecay *= (1 - reduction);
|
||||
|
|
@ -173,34 +263,28 @@ export function usePetSystem() {
|
|||
stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
|
||||
}
|
||||
|
||||
// Random poop generation (更低的機率:約 0.5% per tick)
|
||||
// 平均約每 200 ticks = 10 分鐘拉一次
|
||||
if (state.value !== 'sleep' && Math.random() < 0.005 && stats.value.poopCount < 4 && !isCleaning.value) {
|
||||
// Random poop generation
|
||||
if (state.value !== 'sleep' && Math.random() < rates.poopChance && stats.value.poopCount < 4 && !isCleaning.value) {
|
||||
stats.value.poopCount++;
|
||||
}
|
||||
|
||||
// Health Logic (更溫和的健康下降)
|
||||
// 便便影響健康:每個便便每 tick -0.1 health
|
||||
// Health Logic
|
||||
if (stats.value.poopCount > 0) {
|
||||
stats.value.health = Math.max(0, stats.value.health - (0.1 * stats.value.poopCount));
|
||||
}
|
||||
|
||||
// 飢餓影響健康:飢餓值低於 20 時開始影響健康
|
||||
if (stats.value.hunger < 20) {
|
||||
const hungerPenalty = (20 - stats.value.hunger) * 0.02; // 飢餓越嚴重,扣越多
|
||||
const hungerPenalty = (20 - stats.value.hunger) * 0.02;
|
||||
stats.value.health = Math.max(0, stats.value.health - hungerPenalty);
|
||||
}
|
||||
|
||||
// 不開心影響健康:快樂值低於 20 時開始影響健康(較輕微)
|
||||
if (stats.value.happiness < 20) {
|
||||
const happinessPenalty = (20 - stats.value.happiness) * 0.01;
|
||||
stats.value.health = Math.max(0, stats.value.health - happinessPenalty);
|
||||
}
|
||||
|
||||
// Sickness Check (更低的生病機率)
|
||||
// Destiny Effect: Purification (淨化) - Sickness chance -20%
|
||||
// Deity Buff: 媽祖 - Sickness chance -15%
|
||||
let sickChance = 0.1;
|
||||
// Sickness Check
|
||||
let sickChance = rates.sickChance;
|
||||
if (stats.value.destiny?.id === 'purification') {
|
||||
sickChance *= 0.8;
|
||||
}
|
||||
|
|
@ -211,21 +295,17 @@ export function usePetSystem() {
|
|||
if (stats.value.health < 30 && state.value !== 'sick') {
|
||||
if (Math.random() < sickChance) {
|
||||
state.value = 'sick';
|
||||
api.updatePetStatus({ state: 'sick' });
|
||||
}
|
||||
}
|
||||
|
||||
// Health Recovery (健康值可以緩慢恢復)
|
||||
// 如果沒有便便、飢餓值和快樂值都高,健康值會緩慢恢復
|
||||
// Health Recovery
|
||||
let healthRecovery = 0.05;
|
||||
|
||||
// Deity Buff: 觀音 - Health 回復 +20%
|
||||
if (stats.value.currentDeity === 'guanyin' && stats.value.deityFavors?.guanyin > 0) {
|
||||
healthRecovery *= 1.2;
|
||||
}
|
||||
|
||||
// Deity Buff: 月老 - Happiness 回復 +25%
|
||||
if (stats.value.currentDeity === 'matchmaker' && stats.value.deityFavors?.matchmaker > 0) {
|
||||
// Apply to happiness decay reduction (slower decay = faster recovery)
|
||||
happinessDecay *= 0.75;
|
||||
}
|
||||
|
||||
|
|
@ -233,17 +313,15 @@ export function usePetSystem() {
|
|||
stats.value.health = Math.min(100, stats.value.health + healthRecovery);
|
||||
}
|
||||
|
||||
// Death Check (移除死亡機制,依照之前的討論)
|
||||
// if (stats.value.health === 0) {
|
||||
// state.value = 'dead';
|
||||
// }
|
||||
|
||||
// Evolution / Growth
|
||||
tickCount++;
|
||||
if (tickCount >= TICKS_PER_DAY) {
|
||||
stats.value.age++;
|
||||
tickCount = 0;
|
||||
checkEvolution();
|
||||
|
||||
// Sync stats periodically (e.g., every "day")
|
||||
api.updatePetStatus({ stats: stats.value });
|
||||
}
|
||||
|
||||
checkAchievements();
|
||||
|
|
@ -276,14 +354,20 @@ export function usePetSystem() {
|
|||
triggerState('happy', 2000);
|
||||
}
|
||||
|
||||
function checkEvolution() {
|
||||
async function checkEvolution() {
|
||||
// Simple evolution logic
|
||||
let evolved = false;
|
||||
if (stage.value === 'baby' && stats.value.age >= 3) {
|
||||
stage.value = 'child';
|
||||
triggerState('happy', 2000); // Celebrate
|
||||
evolved = true;
|
||||
} else if (stage.value === 'child' && stats.value.age >= 7) {
|
||||
stage.value = 'adult';
|
||||
triggerState('happy', 2000);
|
||||
evolved = true;
|
||||
}
|
||||
|
||||
if (evolved) {
|
||||
triggerState('happy', 2000); // Celebrate
|
||||
await api.updatePetStatus({ stage: stage.value });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,18 +375,18 @@ export function usePetSystem() {
|
|||
function triggerState(tempState, duration) {
|
||||
const previousState = state.value;
|
||||
state.value = tempState;
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
if (state.value === tempState) { // Only revert if state hasn't changed again
|
||||
state.value = previousState === 'sleep' ? 'idle' : 'idle';
|
||||
// Sync state revert
|
||||
await api.updatePetStatus({ state: state.value });
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function hatchEgg() {
|
||||
async function hatchEgg() {
|
||||
if (stage.value === 'egg') {
|
||||
stage.value = 'baby'; // or 'adult' for now since we only have that sprite
|
||||
// Let's map 'baby' to our 'adult' sprite for now, or just use 'adult'
|
||||
stage.value = 'adult';
|
||||
stage.value = 'adult'; // Skip to adult for demo
|
||||
state.value = 'idle';
|
||||
stats.value.hunger = 50;
|
||||
stats.value.happiness = 50;
|
||||
|
|
@ -313,32 +397,22 @@ export function usePetSystem() {
|
|||
assignDestiny();
|
||||
|
||||
isCleaning.value = false;
|
||||
|
||||
await api.updatePetStatus({
|
||||
stage: stage.value,
|
||||
state: state.value,
|
||||
stats: stats.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
stage.value = 'egg';
|
||||
state.value = 'idle';
|
||||
isCleaning.value = false;
|
||||
stats.value = {
|
||||
hunger: 100,
|
||||
happiness: 100,
|
||||
health: 100,
|
||||
weight: 500,
|
||||
age: 1,
|
||||
poopCount: 0,
|
||||
// v2 Reset
|
||||
str: 0,
|
||||
int: 0,
|
||||
dex: 0,
|
||||
generation: 1,
|
||||
deityFavor: 0,
|
||||
destiny: null
|
||||
};
|
||||
async function reset() {
|
||||
await api.resetGame();
|
||||
await initGame(); // Reload initial data
|
||||
tickCount = 0;
|
||||
}
|
||||
|
||||
function resurrect() {
|
||||
async function resurrect() {
|
||||
if (state.value !== 'dead') return;
|
||||
|
||||
state.value = 'idle';
|
||||
|
|
@ -346,39 +420,45 @@ export function usePetSystem() {
|
|||
stats.value.happiness = 50;
|
||||
stats.value.hunger = 50;
|
||||
|
||||
// Penalty or Ghost Buff?
|
||||
// For now just a console log, maybe visual effect later
|
||||
console.log('Pet Resurrected!');
|
||||
|
||||
await api.updatePetStatus({
|
||||
state: state.value,
|
||||
stats: stats.value
|
||||
});
|
||||
}
|
||||
|
||||
function reincarnate() {
|
||||
async function reincarnate() {
|
||||
// Inherit logic
|
||||
const prevDestiny = stats.value.destiny;
|
||||
const prevFavor = stats.value.deityFavor;
|
||||
const nextGen = (stats.value.generation || 1) + 1;
|
||||
|
||||
// Reset everything
|
||||
reset();
|
||||
await api.resetGame();
|
||||
|
||||
// Reload but keep some stats
|
||||
const petData = await api.getPetStatus();
|
||||
stage.value = petData.stage;
|
||||
state.value = petData.state;
|
||||
stats.value = { ...stats.value, ...petData.stats };
|
||||
|
||||
// Apply Inheritance
|
||||
stats.value.generation = nextGen;
|
||||
|
||||
// 20% Favor inheritance
|
||||
stats.value.deityFavor = Math.floor(prevFavor * 0.2);
|
||||
|
||||
// Destiny Inheritance (Optional: Maybe keep if it was a rare one?)
|
||||
// For now, let's say if you had a Rare (2) destiny, you keep it.
|
||||
// Otherwise, you get a new one on hatch.
|
||||
if (prevDestiny && prevDestiny.rarity === 2) {
|
||||
stats.value.destiny = prevDestiny;
|
||||
}
|
||||
|
||||
console.log('Pet Reincarnated to Gen', nextGen);
|
||||
|
||||
await api.updatePetStatus({ stats: stats.value });
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
onMounted(() => {
|
||||
gameLoopId = setInterval(tick, TICK_RATE);
|
||||
// initGame is called manually or by parent
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
@ -390,6 +470,9 @@ export function usePetSystem() {
|
|||
state,
|
||||
stats,
|
||||
isCleaning,
|
||||
isLoading,
|
||||
error,
|
||||
initGame,
|
||||
feed,
|
||||
play,
|
||||
clean,
|
||||
|
|
@ -398,7 +481,7 @@ export function usePetSystem() {
|
|||
reset,
|
||||
achievements,
|
||||
unlockAllAchievements,
|
||||
assignDestiny, // Export for debug if needed
|
||||
assignDestiny,
|
||||
resurrect,
|
||||
reincarnate
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import { INITIAL_GAME_DATA } from './mockData';
|
||||
|
||||
// Simulate network latency (reducing to 100ms for better UX)
|
||||
const LATENCY = 100;
|
||||
|
||||
// In-memory storage to persist changes during the session
|
||||
let db = JSON.parse(JSON.stringify(INITIAL_GAME_DATA));
|
||||
|
||||
// Helper to simulate async call
|
||||
const mockCall = (data) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(data);
|
||||
}, LATENCY);
|
||||
});
|
||||
};
|
||||
|
||||
export const api = {
|
||||
// --- Pet ---
|
||||
getPetStatus: () => {
|
||||
return mockCall(JSON.parse(JSON.stringify(db.pet)));
|
||||
},
|
||||
|
||||
updatePetStatus: (updates) => {
|
||||
// Deep merge updates into db.pet
|
||||
// Handle top-level properties directly for simplicity in this mock
|
||||
if (updates.stats) Object.assign(db.pet.stats, updates.stats);
|
||||
if (updates.state) db.pet.state = updates.state;
|
||||
if (updates.stage) db.pet.stage = updates.stage;
|
||||
if (updates.evolution) Object.assign(db.pet.evolution, updates.evolution);
|
||||
|
||||
return mockCall({ success: true, pet: db.pet });
|
||||
},
|
||||
|
||||
// --- Inventory ---
|
||||
getInventory: () => {
|
||||
return mockCall(JSON.parse(JSON.stringify(db.inventory)));
|
||||
},
|
||||
|
||||
useItem: (itemId) => {
|
||||
const item = db.inventory.items.find(i => i.id === itemId);
|
||||
if (item && item.count > 0) {
|
||||
item.count--;
|
||||
return mockCall({ success: true, item });
|
||||
}
|
||||
return mockCall({ success: false, message: 'Item not found or empty' });
|
||||
},
|
||||
|
||||
// --- Temple ---
|
||||
getTempleStatus: () => {
|
||||
// Ensure temple structure exists (migration for existing saves if needed)
|
||||
if (!db.pet.stats.deityFavors) {
|
||||
db.pet.stats.deityFavors = { mazu: 0, earthgod: 0, matchmaker: 0, wenchang: 0, guanyin: 0 };
|
||||
}
|
||||
return mockCall({
|
||||
favors: db.pet.stats.deityFavors,
|
||||
dailyPrayerCount: db.pet.stats.dailyPrayerCount || 0,
|
||||
currentDeity: db.pet.stats.currentDeity || 'mazu',
|
||||
deities: db.temple.deities // Return static deity definitions
|
||||
});
|
||||
},
|
||||
|
||||
pray: (deityId) => {
|
||||
if (!db.pet.stats.deityFavors) db.pet.stats.deityFavors = {};
|
||||
|
||||
const currentFavor = db.pet.stats.deityFavors[deityId] || 0;
|
||||
db.pet.stats.deityFavors[deityId] = Math.min(100, currentFavor + 5);
|
||||
db.pet.stats.dailyPrayerCount = (db.pet.stats.dailyPrayerCount || 0) + 1;
|
||||
|
||||
return mockCall({
|
||||
success: true,
|
||||
favors: db.pet.stats.deityFavors,
|
||||
dailyPrayerCount: db.pet.stats.dailyPrayerCount
|
||||
});
|
||||
},
|
||||
|
||||
changeDeity: (deityId) => {
|
||||
db.pet.stats.currentDeity = deityId;
|
||||
return mockCall({ success: true, currentDeity: deityId });
|
||||
},
|
||||
|
||||
// --- Config ---
|
||||
getGameConfig: () => {
|
||||
return mockCall(JSON.parse(JSON.stringify(db.config)));
|
||||
},
|
||||
|
||||
// --- Reset ---
|
||||
resetGame: () => {
|
||||
db = JSON.parse(JSON.stringify(INITIAL_GAME_DATA));
|
||||
return mockCall({ success: true });
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
// 模擬資料庫 - 遊戲初始狀態
|
||||
// 這裡存放所有的遊戲資料,模擬後端資料庫結構
|
||||
export const INITIAL_GAME_DATA = {
|
||||
// --- 寵物資料 (Pet Data) ---
|
||||
pet: {
|
||||
name: "Pet",
|
||||
stage: "egg", // 成長階段: egg (蛋), baby (幼年), adult (成年)
|
||||
state: "idle", // 當前狀態: idle (閒置), sleep (睡覺), eating (進食), sick (生病), dead (死亡)
|
||||
|
||||
// 基礎數值
|
||||
stats: {
|
||||
hunger: 100, // 飢餓度 (0-100): 0 為極度飢餓
|
||||
happiness: 100, // 快樂度 (0-100): 0 為極度憂鬱
|
||||
health: 100, // 健康度 (0-100): 0 為瀕死/生病風險高
|
||||
poopCount: 0, // 畫面上的便便數量
|
||||
age: 0, // 年齡 (天數)
|
||||
weight: 0, // 體重 (克)
|
||||
generation: 1, // 輪迴世代數 (第幾代)
|
||||
|
||||
// V2 新增屬性 (RPG 要素)
|
||||
int: 0, // 智力: 影響猜拳遊戲、事件觸發
|
||||
str: 0, // 力量: 影響訓練遊戲 (火球)
|
||||
dex: 0, // 敏捷: 影響接球遊戲、閃避率
|
||||
karma: 0, // 業力: 影響命運、轉世
|
||||
|
||||
// 神廟相關狀態
|
||||
currentDeity: 'mazu', // 當前參拜的主神 ID
|
||||
dailyPrayerCount: 0, // 今日祈福次數 (每日重置)
|
||||
|
||||
// 各神明的好感度 (0-100)
|
||||
deityFavors: {
|
||||
mazu: 0, // 媽祖
|
||||
earthgod: 0, // 土地公
|
||||
matchmaker: 0, // 月老
|
||||
wenchang: 0, // 文昌帝君
|
||||
guanyin: 0 // 觀音菩薩
|
||||
}
|
||||
},
|
||||
|
||||
// 進化路徑紀錄
|
||||
evolution: {
|
||||
path: [], // 已經歷的進化階段 ID
|
||||
history: [] // 歷史紀錄
|
||||
}
|
||||
},
|
||||
|
||||
// --- 背包系統 (Inventory) ---
|
||||
inventory: {
|
||||
items: [
|
||||
{
|
||||
id: 'medicine',
|
||||
count: 2,
|
||||
name: '萬靈丹',
|
||||
description: '治療生病狀態,恢復健康'
|
||||
},
|
||||
{
|
||||
id: 'snack',
|
||||
count: 5,
|
||||
name: '小餅乾',
|
||||
description: '增加快樂度,但可能導致變胖'
|
||||
},
|
||||
{
|
||||
id: 'cleaner',
|
||||
count: 3,
|
||||
name: '強力清潔劑',
|
||||
description: '瞬間清除所有便便'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// --- 神廟系統 (Temple System) ---
|
||||
temple: {
|
||||
// 神明定義資料 (靜態資料,通常由策劃配置)
|
||||
deities: [
|
||||
{
|
||||
id: 'mazu',
|
||||
name: '媽祖',
|
||||
personality: '溫柔守護',
|
||||
// 加成效果定義
|
||||
buffs: {
|
||||
gameSuccessRate: 0.1, // 小遊戲成功率 +10%
|
||||
sicknessReduction: 0.15 // 生病機率 -15%
|
||||
},
|
||||
// 加成效果描述 (顯示用)
|
||||
buffDescriptions: [
|
||||
'小遊戲成功率 +10%',
|
||||
'生病機率 -15%'
|
||||
],
|
||||
// 神明對話庫
|
||||
dialogues: [
|
||||
"好孩子,媽祖保佑你平安喔",
|
||||
"海上無風浪,心中有媽祖",
|
||||
"要好好照顧寵物啊"
|
||||
],
|
||||
icon: 'deity-mazu' // 對應 CSS class
|
||||
},
|
||||
{
|
||||
id: 'earthgod',
|
||||
name: '土地公',
|
||||
personality: '碎念管家',
|
||||
buffs: {
|
||||
itemDropRate: 0.2, // 掉落物品機率 +20%
|
||||
resourceGain: 0.15 // 資源獲得 +15%
|
||||
},
|
||||
buffDescriptions: [
|
||||
'掉落物品機率 +20%',
|
||||
'資源獲得 +15%'
|
||||
],
|
||||
dialogues: [
|
||||
"又來啦?今天有好好餵寵物嗎?",
|
||||
"欸,地上那個便便怎麼不清一清",
|
||||
"拜我就對了,土地公最靈驗"
|
||||
],
|
||||
icon: 'deity-earthgod'
|
||||
},
|
||||
{
|
||||
id: 'matchmaker',
|
||||
name: '月老',
|
||||
personality: '八卦熱情',
|
||||
buffs: {
|
||||
happinessRecovery: 0.25, // Happiness 回復速度 +25%
|
||||
goodEventRate: 0.1 // 好事件機率 +10%
|
||||
},
|
||||
buffDescriptions: [
|
||||
'Happiness 回復 +25%',
|
||||
'好事件機率 +10%'
|
||||
],
|
||||
dialogues: [
|
||||
"哎呀~你的寵物今天心情不錯喔",
|
||||
"要不要幫你牽條紅線?咦,寵物也需要嗎",
|
||||
"姻緣天注定,開心最重要!"
|
||||
],
|
||||
icon: 'deity-matchmaker'
|
||||
},
|
||||
{
|
||||
id: 'wenchang',
|
||||
name: '文昌帝君',
|
||||
personality: '嚴肅學者',
|
||||
buffs: {
|
||||
intGrowth: 0.3, // INT 成長速度 +30%
|
||||
guessingReward: 0.2 // 猜拳遊戲獎勵 +20%
|
||||
},
|
||||
buffDescriptions: [
|
||||
'INT 成長 +30%',
|
||||
'猜拳獎勵 +20%'
|
||||
],
|
||||
dialogues: [
|
||||
"學海無涯,勤能補拙",
|
||||
"多動腦,少偷懶",
|
||||
"智慧是一切的根本"
|
||||
],
|
||||
icon: 'deity-wenchang'
|
||||
},
|
||||
{
|
||||
id: 'guanyin',
|
||||
name: '觀音菩薩',
|
||||
personality: '慈悲救苦',
|
||||
buffs: {
|
||||
healthRecovery: 0.2, // Health 回復速度 +20%
|
||||
autoHeal: true // 開啟自動治療功能
|
||||
},
|
||||
buffDescriptions: [
|
||||
'Health 回復 +20%',
|
||||
'自動治療 (1次/天)'
|
||||
],
|
||||
dialogues: [
|
||||
"阿彌陀佛,施主請安心",
|
||||
"救苦救難,觀音保佑",
|
||||
"..."
|
||||
],
|
||||
icon: 'deity-guanyin'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// --- 遊戲設定 (Game Config) ---
|
||||
// 包含機率、衰減率等平衡參數
|
||||
config: {
|
||||
rates: {
|
||||
hungerDecay: 0.05, // 飢餓自然衰減率 (每 tick)
|
||||
happinessDecay: 0.08, // 快樂自然衰減率 (每 tick)
|
||||
healthDecay: 0, // 健康自然衰減率 (通常為 0,除非生病)
|
||||
poopChance: 0.005, // 拉屎機率 (每 tick)
|
||||
sickChance: 0.1 // 生病判定基礎機率 (當健康低時)
|
||||
},
|
||||
events: {
|
||||
randomEventChance: 0.01, // 隨機事件觸發機率 (每 tick)
|
||||
// 事件池定義
|
||||
eventPool: [
|
||||
{
|
||||
id: 'found_charm',
|
||||
weight: 10,
|
||||
type: 'good',
|
||||
text: '撿到小符',
|
||||
effect: { happiness: 5 },
|
||||
icon: 'pixel-charm'
|
||||
},
|
||||
{
|
||||
id: 'self_clean',
|
||||
weight: 5,
|
||||
type: 'good',
|
||||
text: '自己掃地',
|
||||
// 特殊效果由邏輯層處理
|
||||
icon: 'pixel-broom'
|
||||
},
|
||||
{
|
||||
id: 'dance',
|
||||
weight: 8,
|
||||
type: 'good',
|
||||
text: '開心地跳舞',
|
||||
effect: { happiness: 10 },
|
||||
icon: 'pixel-note'
|
||||
},
|
||||
{
|
||||
id: 'gift_apple',
|
||||
weight: 5,
|
||||
type: 'good',
|
||||
text: '給你一顆果子',
|
||||
effect: { hunger: 5 },
|
||||
icon: 'pixel-apple'
|
||||
},
|
||||
{
|
||||
id: 'night_noise',
|
||||
weight: 5,
|
||||
type: 'bad',
|
||||
text: '半夜亂叫',
|
||||
effect: { happiness: -5 },
|
||||
icon: 'pixel-angry'
|
||||
},
|
||||
{
|
||||
id: 'overeat',
|
||||
weight: 5,
|
||||
type: 'bad',
|
||||
text: '偷吃太多',
|
||||
effect: { hunger: 20, health: -5 },
|
||||
icon: 'pixel-meat'
|
||||
},
|
||||
{
|
||||
id: 'stomach_ache',
|
||||
weight: 5,
|
||||
type: 'bad',
|
||||
text: '胃痛',
|
||||
effect: { health: -10 },
|
||||
icon: 'pixel-skull'
|
||||
},
|
||||
{
|
||||
id: 'stare',
|
||||
weight: 3,
|
||||
type: 'weird',
|
||||
text: '盯著螢幕外面看...',
|
||||
effect: {},
|
||||
icon: 'pixel-eye'
|
||||
},
|
||||
{
|
||||
id: 'corner_squat',
|
||||
weight: 3,
|
||||
type: 'weird',
|
||||
text: '走到角落蹲著',
|
||||
effect: { happiness: -5 },
|
||||
icon: 'pixel-cloud'
|
||||
},
|
||||
{
|
||||
id: 'glitch',
|
||||
weight: 1,
|
||||
type: 'weird',
|
||||
text: '#@!$%^&*',
|
||||
// 特殊效果由邏輯層處理
|
||||
icon: 'pixel-glitch'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue