Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
00862de989 |
153
src/App.vue
153
src/App.vue
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref } from 'vue';
|
||||||
import DeviceShell from './components/DeviceShell.vue';
|
import DeviceShell from './components/DeviceShell.vue';
|
||||||
import DeviceScreen from './components/DeviceScreen.vue';
|
import DeviceScreen from './components/DeviceScreen.vue';
|
||||||
import PetGame from './components/PetGame.vue';
|
import PetGame from './components/PetGame.vue';
|
||||||
|
|
@ -14,14 +14,25 @@ const debugAction = ref(null); // For passing debug commands to PetGame
|
||||||
|
|
||||||
// Initialize Pet System
|
// Initialize Pet System
|
||||||
const petSystem = usePetSystem();
|
const petSystem = usePetSystem();
|
||||||
|
const {
|
||||||
|
stage,
|
||||||
|
state,
|
||||||
|
stats,
|
||||||
|
feed,
|
||||||
|
play,
|
||||||
|
sleep,
|
||||||
|
clean,
|
||||||
|
isCleaning,
|
||||||
|
hatchEgg,
|
||||||
|
reset,
|
||||||
|
achievements,
|
||||||
|
unlockAllAchievements,
|
||||||
|
resurrect,
|
||||||
|
reincarnate
|
||||||
|
} = petSystem;
|
||||||
|
|
||||||
// Initialize Event System
|
// Initialize Event System
|
||||||
const { currentEvent, checkEventTriggers, triggerRandomEvent, loadEventConfig } = useEventSystem(petSystem);
|
const { currentEvent, checkEventTriggers, triggerRandomEvent } = useEventSystem(petSystem);
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await petSystem.initGame();
|
|
||||||
await loadEventConfig(); // Load event configuration
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start Event Loop
|
// Start Event Loop
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
|
@ -30,40 +41,41 @@ setInterval(() => {
|
||||||
|
|
||||||
|
|
||||||
// Handle Action Menu Events
|
// Handle Action Menu Events
|
||||||
async function handleAction(action) {
|
function handleAction(action) {
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case 'feed':
|
case 'feed':
|
||||||
const feedResult = await petSystem.feed();
|
const feedResult = feed();
|
||||||
// If refused (sick or full), shake head
|
// If refused (sick or full), shake head
|
||||||
if (!feedResult && petGameRef.value) {
|
if (!feedResult && petGameRef.value) {
|
||||||
petGameRef.value.shakeHead();
|
petGameRef.value.shakeHead();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'clean':
|
case 'clean':
|
||||||
await petSystem.clean();
|
clean();
|
||||||
break;
|
break;
|
||||||
case 'play':
|
case 'play':
|
||||||
const playResult = await petSystem.play();
|
if (play()) {
|
||||||
if (playResult && petGameRef.value) {
|
if (petGameRef.value) {
|
||||||
petGameRef.value.startPlaying();
|
petGameRef.value.startPlaying();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'sleep':
|
case 'sleep':
|
||||||
await petSystem.sleep();
|
sleep();
|
||||||
break;
|
break;
|
||||||
case 'medicine':
|
case 'medicine':
|
||||||
// Heal the pet with animation
|
// Heal the pet with animation
|
||||||
if (petSystem.state.value === 'sick') {
|
if (state.value === 'sick') {
|
||||||
if (petGameRef.value) {
|
if (petGameRef.value) {
|
||||||
// Trigger medicine animation
|
// Trigger medicine animation
|
||||||
petGameRef.value.startFeeding('medicine').then(() => {
|
petGameRef.value.startFeeding('medicine').then(() => {
|
||||||
petSystem.stats.value.health = 100;
|
stats.value.health = 100;
|
||||||
petSystem.state.value = 'idle';
|
state.value = 'idle';
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback if ref not ready
|
// Fallback if ref not ready
|
||||||
petSystem.stats.value.health = 100;
|
stats.value.health = 100;
|
||||||
petSystem.state.value = 'idle';
|
state.value = 'idle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -73,10 +85,10 @@ async function handleAction(action) {
|
||||||
break;
|
break;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
// Show reset options
|
// Show reset options
|
||||||
if (petSystem.stage.value === 'egg') {
|
if (stage.value === 'egg') {
|
||||||
await petSystem.hatchEgg();
|
hatchEgg();
|
||||||
} else {
|
} else {
|
||||||
await petSystem.reset();
|
reset();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'jiaobei':
|
case 'jiaobei':
|
||||||
|
|
@ -90,20 +102,19 @@ async function handleAction(action) {
|
||||||
// TODO: 實作求籤邏輯
|
// TODO: 實作求籤邏輯
|
||||||
break;
|
break;
|
||||||
case 'resurrect':
|
case 'resurrect':
|
||||||
await petSystem.resurrect();
|
resurrect();
|
||||||
break;
|
break;
|
||||||
case 'reincarnate':
|
case 'reincarnate':
|
||||||
await petSystem.reincarnate();
|
reincarnate();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Action not implemented:', action);
|
console.log('Action not implemented:', action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Debug/Dev Controls
|
// Debug/Dev Controls
|
||||||
function setPetState(newState) {
|
function setPetState(newState) {
|
||||||
petSystem.state.value = newState;
|
state.value = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerDebugAction(action, payload = null) {
|
function triggerDebugAction(action, payload = null) {
|
||||||
|
|
@ -112,34 +123,21 @@ function triggerDebugAction(action, payload = null) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- Debug: {{ petSystem.isLoading?.value }} - {{ petSystem.error?.value }} -->
|
<DeviceShell>
|
||||||
<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>
|
<DeviceScreen>
|
||||||
<!-- Dynamic Component Switching -->
|
<!-- Dynamic Component Switching -->
|
||||||
<PetGame
|
<PetGame
|
||||||
v-if="currentScreen === 'game'"
|
v-if="currentScreen === 'game'"
|
||||||
ref="petGameRef"
|
ref="petGameRef"
|
||||||
:state="petSystem.state"
|
:state="state"
|
||||||
:stage="petSystem.stage"
|
:stage="stage"
|
||||||
:stats="petSystem.stats"
|
:stats="stats"
|
||||||
:isCleaning="petSystem.isCleaning"
|
:isCleaning="isCleaning"
|
||||||
:showStats="showStats"
|
:showStats="showStats"
|
||||||
:debugAction="debugAction"
|
:debugAction="debugAction"
|
||||||
:achievements="petSystem.achievements"
|
:achievements="achievements"
|
||||||
:currentEvent="currentEvent"
|
:currentEvent="currentEvent"
|
||||||
@update:state="petSystem.state = $event"
|
@update:state="state = $event"
|
||||||
@action="handleAction"
|
@action="handleAction"
|
||||||
/>
|
/>
|
||||||
<Menu v-else />
|
<Menu v-else />
|
||||||
|
|
@ -157,12 +155,12 @@ function triggerDebugAction(action, payload = null) {
|
||||||
<button @click="setPetState('dead')">Dead</button>
|
<button @click="setPetState('dead')">Dead</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button @click="petSystem.stats.poopCount = Math.min((petSystem.stats.poopCount || 0) + 1, 4)">💩 Add Poop</button>
|
<button @click="stats.poopCount = Math.min((stats.poopCount || 0) + 1, 4)">💩 Add Poop</button>
|
||||||
<button @click="petSystem.stats.poopCount = 0">🧼 Clean All</button>
|
<button @click="stats.poopCount = 0">🧼 Clean All</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button v-if="petSystem.stage === 'egg'" @click="petSystem.hatchEgg()">🥚 Hatch Egg</button>
|
<button v-if="stage === 'egg'" @click="hatchEgg()">🥚 Hatch Egg</button>
|
||||||
<button v-else @click="petSystem.reset()">🔄 Reset to Egg</button>
|
<button v-else @click="reset()">🔄 Reset to Egg</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button @click="triggerDebugAction('randomEvent')">🎲 Random Event</button>
|
<button @click="triggerDebugAction('randomEvent')">🎲 Random Event</button>
|
||||||
|
|
@ -175,69 +173,16 @@ function triggerDebugAction(action, payload = null) {
|
||||||
<button @click="triggerDebugAction('setMood', 'sad')">😢 Sad</button>
|
<button @click="triggerDebugAction('setMood', 'sad')">😢 Sad</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button @click="petSystem.unlockAllAchievements()">🏆 Unlock All Achievements</button>
|
<button @click="unlockAllAchievements()">🏆 Unlock All Achievements</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button @click="petSystem.stats.dailyPrayerCount = 0">🙏 Reset Prayer Count</button>
|
<button @click="stats.dailyPrayerCount = 0">🙏 Reset Prayer Count</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.debug-controls {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="action-menu">
|
<div class="action-menu">
|
||||||
<button class="icon-btn icon-clean" @click="$emit('clean')" :disabled="disabled || isSleeping || poopCount === 0" title="清理"></button>
|
<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 || isSleeping || !isSick" 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="isSleeping ? '開燈' : '關燈'"></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 || isSleeping" title="背包"></button>
|
<button class="icon-btn icon-backpack" @click="$emit('inventory')" :disabled="disabled" title="背包"></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -13,10 +13,6 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
isSleeping: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
poopCount: {
|
poopCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
|
|
@ -39,14 +35,14 @@ defineEmits(['clean', 'medicine', 'sleep', 'inventory']);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 19px;
|
padding: 6px 12px;
|
||||||
background: rgba(155, 188, 15, 0.05);
|
background: rgba(155, 188, 15, 0.05);
|
||||||
border-top: 3px solid rgba(0, 0, 0, 0.1);
|
border-top: 2px solid rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
width: 26px;
|
width: 16px;
|
||||||
height: 26px;
|
height: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -59,112 +55,88 @@ defineEmits(['clean', 'medicine', 'sleep', 'inventory']);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clean Icon (精美掃把) */
|
/* Clean Icon (Broom) */
|
||||||
.icon-clean::before {
|
.icon-clean::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.2px;
|
width: 2px;
|
||||||
height: 3.2px;
|
height: 2px;
|
||||||
background: #8B4513;
|
background: #8B4513;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 掃把柄 */
|
0px -6px 0 #8B4513, 0px -4px 0 #8B4513,
|
||||||
0px -12.8px 0 #8B4513, 0px -9.6px 0 #8B4513, 0px -6.4px 0 #8B4513,
|
0px -2px 0 #8B4513, 0px 0px 0 #8B4513,
|
||||||
0px -3.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,
|
||||||
-1.6px 3.2px 0 #8B4513, 0px 3.2px 0 #8B4513, 1.6px 3.2px 0 #8B4513,
|
-2px 6px 0 #ffcc00, 0px 6px 0 #ffcc00, 2px 6px 0 #ffcc00;
|
||||||
/* 掃把頭 (左側) */
|
|
||||||
-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 (精美藥丸) */
|
/* Medicine Icon (Pill/Cross) */
|
||||||
.icon-medicine::before {
|
.icon-medicine::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.2px;
|
width: 2px;
|
||||||
height: 3.2px;
|
height: 2px;
|
||||||
background: #ff6b6b;
|
background: #ff4444;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 藥丸上半部 (圓形) */
|
0px -6px 0 #ff4444, 0px -4px 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,
|
-4px -2px 0 #ff4444, -2px -2px 0 #ff4444, 0px -2px 0 #ff4444, 2px -2px 0 #ff4444, 4px -2px 0 #ff4444,
|
||||||
-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,
|
0px 0px 0 #ff4444, 0px 2px 0 #ff4444,
|
||||||
-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,
|
0px 4px 0 #ff4444, 0px 6px 0 #ff4444;
|
||||||
/* 藥丸中間 (十字) */
|
|
||||||
-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 (精美燈泡) */
|
/* Sleep Icon (Light Bulb/燈泡) - Enhanced */
|
||||||
.icon-sleep::before {
|
.icon-sleep::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.2px;
|
width: 2px;
|
||||||
height: 3.2px;
|
height: 2px;
|
||||||
background: #ffd700;
|
background: transparent;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 燈泡主體 (圓形,帶高光) */
|
/* 燈泡主體 (圓形) */
|
||||||
-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,
|
-2px -6px 0 #ffd700, 0px -6px 0 #ffd700, 2px -6px 0 #ffd700,
|
||||||
-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,
|
-4px -4px 0 #ffd700, -2px -4px 0 #ffd700, 0px -4px 0 #ffd700, 2px -4px 0 #ffd700, 4px -4px 0 #ffd700,
|
||||||
-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,
|
-4px -2px 0 #ffd700, -2px -2px 0 #ffd700, 0px -2px 0 #ffd700, 2px -2px 0 #ffd700, 4px -2px 0 #ffd700,
|
||||||
-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,
|
-4px 0px 0 #ffd700, -2px 0px 0 #ffd700, 0px 0px 0 #ffd700, 2px 0px 0 #ffd700, 4px 0px 0 #ffd700,
|
||||||
-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,
|
-2px 2px 0 #ffd700, 0px 2px 0 #ffd700, 2px 2px 0 #ffd700,
|
||||||
-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,
|
|
||||||
/* 燈泡底部 (螺旋) */
|
/* 燈泡底部 (螺旋) */
|
||||||
-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,
|
-1px 4px 0 #8B4513, 0px 4px 0 #8B4513, 1px 4px 0 #8B4513,
|
||||||
-1.6px 12.8px 0 #8B4513, 0px 12.8px 0 #654321, 1.6px 12.8px 0 #8B4513,
|
/* 光線 (向下) */
|
||||||
/* 光線 (向下發散) */
|
0px 6px 0 #ffd700, 0px 8px 0 #ffd700;
|
||||||
-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 {
|
.icon-backpack::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.2px;
|
width: 2px;
|
||||||
height: 3.2px;
|
height: 2px;
|
||||||
background: #8d6e63;
|
background: #8d6e63;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 頂部翻蓋 */
|
/* Top flap */
|
||||||
-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,
|
-2px -6px 0 #8d6e63, 0px -6px 0 #8d6e63, 2px -6px 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,
|
-4px -4px 0 #8d6e63, -2px -4px 0 #a1887f, 0px -4px 0 #a1887f, 2px -4px 0 #a1887f, 4px -4px 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,
|
/* Body */
|
||||||
-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,
|
-4px -2px 0 #8d6e63, -2px -2px 0 #5d4037, 0px -2px 0 #5d4037, 2px -2px 0 #5d4037, 4px -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,
|
-4px 0px 0 #8d6e63, -2px 0px 0 #5d4037, 0px 0px 0 #5d4037, 2px 0px 0 #5d4037, 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,
|
-4px 2px 0 #8d6e63, -2px 2px 0 #5d4037, 0px 2px 0 #5d4037, 2px 2px 0 #5d4037, 4px 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,
|
-4px 4px 0 #8d6e63, -2px 4px 0 #5d4037, 0px 4px 0 #5d4037, 2px 4px 0 #5d4037, 4px 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,
|
/* Bottom */
|
||||||
-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,
|
-2px 6px 0 #8d6e63, 0px 6px 0 #8d6e63, 2px 6px 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>
|
<style scoped>
|
||||||
/* 外殼 */
|
/* 外殼 */
|
||||||
.device {
|
.device {
|
||||||
width: 448px;
|
width: 280px;
|
||||||
height: 544px;
|
height: 340px;
|
||||||
border-radius: 60% 60% 55% 55%;
|
border-radius: 60% 60% 55% 55%;
|
||||||
background: radial-gradient(circle at 30% 0%, #ffe7ff, #ffc6f1);
|
background: radial-gradient(circle at 30% 0%, #ffe7ff, #ffc6f1);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 29px 64px rgba(0, 0, 0, 0.35),
|
0 18px 40px rgba(0, 0, 0, 0.35),
|
||||||
inset 0 6px 16px rgba(255, 255, 255, 0.8);
|
inset 0 4px 10px rgba(255, 255, 255, 0.8);
|
||||||
padding-top: 61px;
|
padding-top: 38px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 29px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-title {
|
.device-title {
|
||||||
font-size: 22px;
|
font-size: 14px;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-wrapper {
|
.screen-wrapper {
|
||||||
width: 336px;
|
width: 210px;
|
||||||
height: 256px;
|
height: 160px;
|
||||||
border-radius: 29px;
|
border-radius: 18px;
|
||||||
background: #888;
|
background: #888;
|
||||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
|
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
|
||||||
padding: 10px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 26px;
|
gap: 16px;
|
||||||
margin-top: 6px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
width: 48px;
|
width: 30px;
|
||||||
height: 48px;
|
height: 30px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: radial-gradient(circle at 30% 20%, #fff8e7, #f3a3af);
|
background: radial-gradient(circle at 30% 20%, #fff8e7, #f3a3af);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 6px 0 #c26c7a,
|
0 4px 0 #c26c7a,
|
||||||
0 6px 13px rgba(0, 0, 0, 0.35);
|
0 4px 8px rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<!-- Top Menu -->
|
<!-- Top Menu -->
|
||||||
<TopMenu
|
<TopMenu
|
||||||
:disabled="stage === 'egg'"
|
:disabled="stage === 'egg'"
|
||||||
:isSleeping="state === 'sleep'"
|
|
||||||
@info="showPetInfo = !showPetInfo"
|
@info="showPetInfo = !showPetInfo"
|
||||||
@feed="$emit('action', 'feed')"
|
@feed="$emit('action', 'feed')"
|
||||||
@playMenu="showPlayMenu = true"
|
@playMenu="showPlayMenu = true"
|
||||||
|
|
@ -178,8 +177,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Jiaobei Animation (覆蓋遊戲區域) -->
|
<!-- Jiaobei Animation (覆蓋遊戲區域) -->
|
||||||
<JiaobeiAnimation
|
<JiaobeiAnimation
|
||||||
v-if="showJiaobeiAnimation"
|
v-if="showJiaobeiAnimation"
|
||||||
|
|
@ -279,7 +276,6 @@
|
||||||
<!-- Action Menu (Bottom) -->
|
<!-- Action Menu (Bottom) -->
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
:disabled="stage === 'egg'"
|
:disabled="stage === 'egg'"
|
||||||
:isSleeping="state === 'sleep'"
|
|
||||||
:poopCount="stats?.poopCount || 0"
|
:poopCount="stats?.poopCount || 0"
|
||||||
:health="stats?.health || 100"
|
:health="stats?.health || 100"
|
||||||
:isSick="state === 'sick'"
|
:isSick="state === 'sick'"
|
||||||
|
|
@ -945,7 +941,7 @@ const FOOD_PALETTE = FOOD_OPTIONS[currentFood].palette;
|
||||||
|
|
||||||
const CURRENT_PRESET = SPRITE_PRESETS.tinyTigerCatB;
|
const CURRENT_PRESET = SPRITE_PRESETS.tinyTigerCatB;
|
||||||
const FULL_PRESET = FULL_PRESETS.tinyTigerCatB;
|
const FULL_PRESET = FULL_PRESETS.tinyTigerCatB;
|
||||||
const pixelSize = Math.round(CURRENT_PRESET.pixelSize * 1.6);
|
const pixelSize = CURRENT_PRESET.pixelSize;
|
||||||
|
|
||||||
// Calculate base stats based on current stage
|
// Calculate base stats based on current stage
|
||||||
const baseStats = computed(() => {
|
const baseStats = computed(() => {
|
||||||
|
|
@ -1568,8 +1564,8 @@ defineExpose({
|
||||||
}
|
}
|
||||||
@keyframes pet-sick-shake {
|
@keyframes pet-sick-shake {
|
||||||
0%, 100% { transform: translateX(0); }
|
0%, 100% { transform: translateX(0); }
|
||||||
25% { transform: translateX(-3.2px); }
|
25% { transform: translateX(-2px); }
|
||||||
75% { transform: translateX(3.2px); }
|
75% { transform: translateX(2px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tail */
|
/* Tail */
|
||||||
|
|
@ -1580,17 +1576,17 @@ defineExpose({
|
||||||
|
|
||||||
@keyframes tail-wag-idle {
|
@keyframes tail-wag-idle {
|
||||||
0% { transform: translateX(0px); }
|
0% { transform: translateX(0px); }
|
||||||
50% { transform: translateX(1.6px); }
|
50% { transform: translateX(1px); }
|
||||||
100% { transform: translateX(0px); }
|
100% { transform: translateX(0px); }
|
||||||
}
|
}
|
||||||
@keyframes tail-wag-sleep {
|
@keyframes tail-wag-sleep {
|
||||||
0% { transform: translateX(0px); }
|
0% { transform: translateX(0px); }
|
||||||
50% { transform: translateX(0.8px); }
|
50% { transform: translateX(0.5px); }
|
||||||
100% { transform: translateX(0px); }
|
100% { transform: translateX(0px); }
|
||||||
}
|
}
|
||||||
@keyframes tail-wag-sick {
|
@keyframes tail-wag-sick {
|
||||||
0% { transform: translateX(0px); }
|
0% { transform: translateX(0px); }
|
||||||
50% { transform: translateX(1.3px); }
|
50% { transform: translateX(0.8px); }
|
||||||
100% { transform: translateX(0px); }
|
100% { transform: translateX(0px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1602,15 +1598,15 @@ defineExpose({
|
||||||
|
|
||||||
@keyframes leg-front-step {
|
@keyframes leg-front-step {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-1.6px); }
|
50% { transform: translateY(-1px); }
|
||||||
}
|
}
|
||||||
@keyframes leg-back-step {
|
@keyframes leg-back-step {
|
||||||
0%, 100% { transform: translateY(-1.6px); }
|
0%, 100% { transform: translateY(-1px); }
|
||||||
50% { transform: translateY(0); }
|
50% { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
@keyframes leg-sick-step {
|
@keyframes leg-sick-step {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-0.8px); }
|
50% { transform: translateY(-0.5px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ears */
|
/* Ears */
|
||||||
|
|
@ -1620,7 +1616,7 @@ defineExpose({
|
||||||
|
|
||||||
@keyframes ear-twitch {
|
@keyframes ear-twitch {
|
||||||
0%, 90%, 100% { transform: translateY(0); }
|
0%, 90%, 100% { transform: translateY(0); }
|
||||||
92% { transform: translateY(-1.6px); }
|
92% { transform: translateY(-1px); }
|
||||||
96% { transform: translateY(0); }
|
96% { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1634,7 +1630,7 @@ defineExpose({
|
||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark Overlay (關燈效果 - 覆蓋整個遊戲容器) */
|
/* Dark Overlay (關燈效果 - 只在遊戲區域,top menu 和 bottom menu 之間) */
|
||||||
.dark-overlay {
|
.dark-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -1644,11 +1640,9 @@ defineExpose({
|
||||||
background: #000;
|
background: #000;
|
||||||
z-index: 7; /* Above poop (5), Below ZZZ (10) */
|
z-index: 7; /* Above poop (5), Below ZZZ (10) */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
border-radius: 10px; /* 與螢幕邊框一致 */
|
border-radius: 10px; /* Match screen border */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Sleep ZZZ */
|
/* Sleep ZZZ */
|
||||||
.sleep-zzz {
|
.sleep-zzz {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -1658,112 +1652,30 @@ defineExpose({
|
||||||
z-index: 10; /* Above dark overlay */
|
z-index: 10; /* Above dark overlay */
|
||||||
}
|
}
|
||||||
.sleep-zzz.dark-mode {
|
.sleep-zzz.dark-mode {
|
||||||
color: #fff; /* 在黑色背景下變白色 */
|
color: #fff !important; /* 強制白色:在黑色背景下變白色 */
|
||||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.8);
|
text-shadow: 0 0 4px rgba(255, 255, 255, 0.8); /* 添加發光效果讓它更明顯 */
|
||||||
}
|
}
|
||||||
.sleep-zzz span {
|
.sleep-zzz span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 16px;
|
font-size: 10px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: zzz-float 3s ease-in-out infinite;
|
animation: zzz-float 3s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
.sleep-zzz .z1 { left: 0; animation-delay: 0s; }
|
.sleep-zzz .z1 { left: 0; animation-delay: 0s; }
|
||||||
.sleep-zzz .z2 { left: 13px; animation-delay: 0.8s; }
|
.sleep-zzz .z2 { left: 8px; animation-delay: 0.8s; }
|
||||||
.sleep-zzz .z3 { left: 24px; animation-delay: 1.6s; }
|
.sleep-zzz .z3 { left: 15px; animation-delay: 1.6s; }
|
||||||
|
|
||||||
@keyframes zzz-float {
|
@keyframes zzz-float {
|
||||||
0% { opacity: 0; transform: translateY(0) scale(0.7); }
|
0% { opacity: 0; transform: translateY(0) scale(0.7); }
|
||||||
20% { opacity: 1; transform: translateY(-6.4px) scale(0.9); }
|
20% { opacity: 1; transform: translateY(-4px) scale(0.9); }
|
||||||
80% { opacity: 1; transform: translateY(-25.6px) scale(1.05); }
|
80% { opacity: 1; transform: translateY(-16px) scale(1.05); }
|
||||||
100% { opacity: 0; transform: translateY(-38.4px) scale(1.1); }
|
100% { opacity: 0; transform: translateY(-24px) 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 */
|
||||||
.sick-icon {
|
.sick-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 22px;
|
font-size: 14px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
animation: sick-icon-pulse 1.2s ease-in-out infinite;
|
animation: sick-icon-pulse 1.2s ease-in-out infinite;
|
||||||
|
|
@ -1780,11 +1692,11 @@ defineExpose({
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 48px;
|
width: 30px;
|
||||||
height: 64px;
|
height: 40px;
|
||||||
background: linear-gradient(to bottom, #888 0%, #555 100%);
|
background: linear-gradient(to bottom, #888 0%, #555 100%);
|
||||||
border-radius: 16px 16px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
border: 3px solid #333;
|
border: 2px solid #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tombstone::before {
|
.tombstone::before {
|
||||||
|
|
@ -1793,7 +1705,7 @@ defineExpose({
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
font-size: 13px;
|
font-size: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
@ -1801,55 +1713,55 @@ defineExpose({
|
||||||
/* Poop Sprite (Larger & More Detailed) */
|
/* Poop Sprite (Larger & More Detailed) */
|
||||||
.poop {
|
.poop {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 51px;
|
width: 32px;
|
||||||
height: 51px;
|
height: 32px;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poop-sprite {
|
.poop-sprite {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 4.8px;
|
width: 3px;
|
||||||
height: 4.8px;
|
height: 3px;
|
||||||
background: #3d2817;
|
background: #3d2817;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* Top point */
|
/* Top point */
|
||||||
0px -19.2px 0 #3d2817,
|
0px -12px 0 #3d2817,
|
||||||
/* Row 2 - narrow */
|
/* Row 2 - narrow */
|
||||||
-4.8px -14.4px 0 #3d2817, 0px -14.4px 0 #5a4028, 4.8px -14.4px 0 #3d2817,
|
-3px -9px 0 #3d2817, 0px -9px 0 #5a4028, 3px -9px 0 #3d2817,
|
||||||
/* Row 3 */
|
/* Row 3 */
|
||||||
-9.6px -9.6px 0 #3d2817, -4.8px -9.6px 0 #5a4028, 0px -9.6px 0 #6b4e38,
|
-6px -6px 0 #3d2817, -3px -6px 0 #5a4028, 0px -6px 0 #6b4e38,
|
||||||
4.8px -9.6px 0 #5a4028, 9.6px -9.6px 0 #3d2817,
|
3px -6px 0 #5a4028, 6px -6px 0 #3d2817,
|
||||||
/* Row 4 - eyes */
|
/* Row 4 - eyes */
|
||||||
-9.6px -4.8px 0 #3d2817, -4.8px -4.8px 0 #ffffff, 0px -4.8px 0 #6b4e38,
|
-6px -3px 0 #3d2817, -3px -3px 0 #ffffff, 0px -3px 0 #6b4e38,
|
||||||
4.8px -4.8px 0 #ffffff, 9.6px -4.8px 0 #3d2817,
|
3px -3px 0 #ffffff, 6px -3px 0 #3d2817,
|
||||||
/* Row 5 - middle */
|
/* Row 5 - middle */
|
||||||
-14.4px 0px 0 #3d2817, -9.6px 0px 0 #5a4028, -4.8px 0px 0 #6b4e38, 0px 0px 0 #7d5a3a,
|
-9px 0px 0 #3d2817, -6px 0px 0 #5a4028, -3px 0px 0 #6b4e38, 0px 0px 0 #7d5a3a,
|
||||||
4.8px 0px 0 #6b4e38, 9.6px 0px 0 #5a4028, 14.4px 0px 0 #3d2817,
|
3px 0px 0 #6b4e38, 6px 0px 0 #5a4028, 9px 0px 0 #3d2817,
|
||||||
/* Row 6 */
|
/* Row 6 */
|
||||||
-14.4px 4.8px 0 #3d2817, -9.6px 4.8px 0 #5a4028, -4.8px 4.8px 0 #6b4e38, 0px 4.8px 0 #7d5a3a,
|
-9px 3px 0 #3d2817, -6px 3px 0 #5a4028, -3px 3px 0 #6b4e38, 0px 3px 0 #7d5a3a,
|
||||||
4.8px 4.8px 0 #6b4e38, 9.6px 4.8px 0 #5a4028, 14.4px 4.8px 0 #3d2817,
|
3px 3px 0 #6b4e38, 6px 3px 0 #5a4028, 9px 3px 0 #3d2817,
|
||||||
/* Row 7 */
|
/* Row 7 */
|
||||||
-19.2px 9.6px 0 #3d2817, -14.4px 9.6px 0 #3d2817, -9.6px 9.6px 0 #5a4028, -4.8px 9.6px 0 #6b4e38,
|
-12px 6px 0 #3d2817, -9px 6px 0 #3d2817, -6px 6px 0 #5a4028, -3px 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,
|
0px 6px 0 #7d5a3a, 3px 6px 0 #6b4e38, 6px 6px 0 #5a4028, 9px 6px 0 #3d2817, 12px 6px 0 #3d2817,
|
||||||
/* Row 8 - wider */
|
/* Row 8 - wider */
|
||||||
-19.2px 14.4px 0 #3d2817, -14.4px 14.4px 0 #5a4028, -9.6px 14.4px 0 #6b4e38, -4.8px 14.4px 0 #7d5a3a,
|
-12px 9px 0 #3d2817, -9px 9px 0 #5a4028, -6px 9px 0 #6b4e38, -3px 9px 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,
|
0px 9px 0 #7d5a3a, 3px 9px 0 #7d5a3a, 6px 9px 0 #6b4e38, 9px 9px 0 #5a4028, 12px 9px 0 #3d2817,
|
||||||
/* Bottom row */
|
/* Bottom row */
|
||||||
-14.4px 19.2px 0 #3d2817, -9.6px 19.2px 0 #5a4028, -4.8px 19.2px 0 #6b4e38,
|
-9px 12px 0 #3d2817, -6px 12px 0 #5a4028, -3px 12px 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;
|
0px 12px 0 #6b4e38, 3px 12px 0 #6b4e38, 6px 12px 0 #5a4028, 9px 12px 0 #3d2817;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stink Animation (3 Fingers Style - Wavy) */
|
/* Stink Animation (3 Fingers Style - Wavy) */
|
||||||
.poop-stink {
|
.poop-stink {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -40px;
|
top: -25px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 64px;
|
width: 40px;
|
||||||
height: 48px;
|
height: 30px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1857,18 +1769,18 @@ defineExpose({
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
width: 3px;
|
width: 2px;
|
||||||
height: 3px;
|
height: 2px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* Pixel art vertical wave pattern */
|
/* Pixel art vertical wave pattern */
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0px 0px 0 #555,
|
0px 0px 0 #555,
|
||||||
1.6px -3.2px 0 #555,
|
1px -2px 0 #555,
|
||||||
1.6px -6.4px 0 #555,
|
1px -4px 0 #555,
|
||||||
0px -9.6px 0 #555,
|
0px -6px 0 #555,
|
||||||
-1.6px -12.8px 0 #555,
|
-1px -8px 0 #555,
|
||||||
-1.6px -16px 0 #555,
|
-1px -10px 0 #555,
|
||||||
0px -19.2px 0 #555;
|
0px -12px 0 #555;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform-origin: bottom center;
|
transform-origin: bottom center;
|
||||||
}
|
}
|
||||||
|
|
@ -1880,32 +1792,45 @@ defineExpose({
|
||||||
|
|
||||||
/* Middle Finger */
|
/* Middle Finger */
|
||||||
.stink-line.s2 {
|
.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: stink-finger-2 2s ease-in-out infinite;
|
||||||
animation-delay: 0.3s;
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Right Finger */
|
/* Right Finger */
|
||||||
.stink-line.s3 {
|
.stink-line.s3 {
|
||||||
animation: stink-finger-3 2s ease-in-out infinite;
|
animation: stink-finger-3 2s ease-in-out infinite;
|
||||||
animation-delay: 0.6s;
|
animation-delay: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes stink-finger-1 {
|
@keyframes stink-finger-1 {
|
||||||
0% { opacity: 0; transform: translateX(-16px) scaleY(0.2); }
|
0% { opacity: 0; transform: translateX(-50%) rotate(-25deg) scaleY(0.5); }
|
||||||
50% { opacity: 0.6; transform: translateX(-24px) scaleY(1); }
|
20% { opacity: 0.8; transform: translateX(-50%) rotate(-25deg) scaleY(1) translateY(-5px); }
|
||||||
100% { opacity: 0; transform: translateX(-32px) scaleY(1.2); }
|
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); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes stink-finger-2 {
|
@keyframes stink-finger-2 {
|
||||||
0% { opacity: 0; transform: translateX(0) scaleY(0.2); }
|
0% { opacity: 0; transform: translateX(-50%) rotate(0deg) scaleY(0.5); }
|
||||||
50% { opacity: 0.6; transform: translateX(0) scaleY(1.2); }
|
20% { opacity: 0.8; transform: translateX(-50%) rotate(0deg) scaleY(1) translateY(-6px); }
|
||||||
100% { opacity: 0; transform: translateX(0) scaleY(1.4); }
|
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); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes stink-finger-3 {
|
@keyframes stink-finger-3 {
|
||||||
0% { opacity: 0; transform: translateX(16px) scaleY(0.2); }
|
0% { opacity: 0; transform: translateX(-50%) rotate(25deg) scaleY(0.5); }
|
||||||
50% { opacity: 0.6; transform: translateX(24px) scaleY(1); }
|
20% { opacity: 0.8; transform: translateX(-50%) rotate(25deg) scaleY(1) translateY(-5px); }
|
||||||
100% { opacity: 0; transform: translateX(32px) scaleY(1.2); }
|
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); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Poop Flush Animation */
|
/* Poop Flush Animation */
|
||||||
|
|
@ -2003,14 +1928,14 @@ defineExpose({
|
||||||
}
|
}
|
||||||
|
|
||||||
.death-menu {
|
.death-menu {
|
||||||
margin-top: -48px; /* Overlay on top of tombstone */
|
margin-top: -30px; /* Overlay on top of tombstone */
|
||||||
background: rgba(224, 224, 224, 0.95);
|
background: rgba(224, 224, 224, 0.95);
|
||||||
border: 6px solid #555;
|
border: 4px solid #555;
|
||||||
padding: 16px;
|
padding: 10px;
|
||||||
border-radius: 13px;
|
border-radius: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 6px 0 #333;
|
box-shadow: 0 4px 0 #333;
|
||||||
max-width: 288px;
|
max-width: 180px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -2019,61 +1944,39 @@ defineExpose({
|
||||||
|
|
||||||
.death-title {
|
.death-title {
|
||||||
font-family: 'DotGothic16', monospace;
|
font-family: 'DotGothic16', monospace;
|
||||||
font-size: 22px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 13px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.death-actions {
|
.death-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.death-btn {
|
.action-btn {
|
||||||
background: #fff;
|
|
||||||
border: 3px solid #333;
|
|
||||||
padding: 13px;
|
|
||||||
font-family: 'DotGothic16', monospace;
|
font-family: 'DotGothic16', monospace;
|
||||||
font-size: 19px;
|
padding: 6px 10px;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #888;
|
||||||
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
font-size: 11px;
|
||||||
transition: all 0.1s;
|
transition: all 0.1s;
|
||||||
|
white-space: normal;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.death-btn:hover {
|
.action-btn:active {
|
||||||
background: #eee;
|
transform: translateY(2px);
|
||||||
transform: translateY(-2px);
|
background: #ddd;
|
||||||
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 */
|
/* Mood Animations */
|
||||||
.pet-root.mood-happy {
|
.pet-root.mood-happy {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="top-menu">
|
<div class="top-menu">
|
||||||
<button class="icon-btn icon-stats" @click="$emit('info')" title="Status"></button>
|
<button class="icon-btn icon-stats" @click="$emit('info')" title="Status"></button>
|
||||||
<button class="icon-btn icon-feed" @click="$emit('feed')" :disabled="disabled || isSleeping" title="Feed"></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 || isSleeping" title="Play"></button> -->
|
<button class="icon-btn icon-play" @click="$emit('playMenu')" :disabled="disabled" 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>
|
<button class="icon-btn icon-temple" @click="$emit('temple')" :disabled="disabled" title="Temple"></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import IconGamepad from './icon/game.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
|
||||||
isSleeping: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -32,14 +23,14 @@ defineEmits(['info', 'feed', 'playMenu', 'temple']);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 16px;
|
padding: 6px 12px;
|
||||||
background: rgba(155, 188, 15, 0.05);
|
background: rgba(155, 188, 15, 0.05);
|
||||||
border-bottom: 3px solid rgba(0, 0, 0, 0.08);
|
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
width: 26px;
|
width: 16px;
|
||||||
height: 26px;
|
height: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -52,205 +43,79 @@ defineEmits(['info', 'feed', 'playMenu', 'temple']);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 共用:2px 為一個 pixel 單位 */
|
/* Stats Icon (Bar Chart) */
|
||||||
.icon-btn::before {
|
.icon-stats::before {
|
||||||
content: "";
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
|
background: #333;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
|
||||||
|
|
||||||
/* ===================== Stats:精緻眼鏡 ===================== */
|
|
||||||
.icon-stats::before {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 左框外框 */
|
-6px 4px 0 #333, -6px 2px 0 #333, -6px 0px 0 #333,
|
||||||
-10px -6px 0 #000, -8px -6px 0 #000, -6px -6px 0 #000,
|
-2px 4px 0 #333, -2px 2px 0 #333, -2px 0px 0 #333, -2px -2px 0 #333,
|
||||||
-12px -4px 0 #000, -12px -2px 0 #000, -12px 0px 0 #000, -12px 2px 0 #000,
|
2px 4px 0 #333, 2px 2px 0 #333, 2px 0px 0 #333, 2px -2px 0 #333, 2px -4px 0 #333,
|
||||||
-10px 4px 0 #000, -8px 4px 0 #000, -6px 4px 0 #000,
|
6px 4px 0 #333, 6px 2px 0 #333;
|
||||||
-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:精緻壽司 ===================== */
|
/* Feed Icon (Apple/Food) */
|
||||||
.icon-feed::before {
|
.icon-feed::before {
|
||||||
background: transparent;
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
background: #ff4444;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 海苔外框 */
|
-2px -6px 0 #228822,
|
||||||
-12px -4px 0 #000, -10px -4px 0 #000, -8px -4px 0 #000, -6px -4px 0 #000,
|
-4px -2px 0 #ff4444, -2px -2px 0 #ff4444, 0px -2px 0 #ff4444, 2px -2px 0 #ff4444,
|
||||||
-4px -4px 0 #000, -2px -4px 0 #000, 0px -4px 0 #000, 2px -4px 0 #000,
|
-4px 0px 0 #ff4444, -2px 0px 0 #ff4444, 0px 0px 0 #ff4444, 2px 0px 0 #ff4444, 4px 0px 0 #ff4444,
|
||||||
4px -4px 0 #000, 6px -4px 0 #000, 8px -4px 0 #000, 10px -4px 0 #000,
|
-4px 2px 0 #ff4444, -2px 2px 0 #ff4444, 0px 2px 0 #ff4444, 2px 2px 0 #ff4444, 4px 2px 0 #ff4444,
|
||||||
-12px -2px 0 #000, 10px -2px 0 #000,
|
-2px 4px 0 #ff4444, 0px 4px 0 #ff4444, 2px 4px 0 #ff4444;
|
||||||
-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:灰色像素手把(依原圖重製,十字鍵加粗) */
|
/* Play Icon (Ball/Game) */
|
||||||
.icon-play::before {
|
.icon-play::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
|
background: #4444ff;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
background: transparent;
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* ===== 線材(上方那一根線) ===== */
|
-2px -4px 0 #4444ff, 0px -4px 0 #4444ff, 2px -4px 0 #4444ff,
|
||||||
0px -12px 0 #000,
|
-4px -2px 0 #4444ff, -2px -2px 0 #4444ff, 0px -2px 0 #4444ff, 2px -2px 0 #4444ff, 4px -2px 0 #4444ff,
|
||||||
0px -10px 0 #000,
|
-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;
|
||||||
-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:精細小廟 ===================== */
|
/* Temple Icon (廟宇) */
|
||||||
.icon-temple::before {
|
.icon-temple::before {
|
||||||
background: transparent;
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
background: #D2691E;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
/* 屋頂尖端 */
|
/* 屋頂 */
|
||||||
-2px -14px 0 #4e342e, 0px -14px 0 #4e342e, 2px -14px 0 #4e342e,
|
-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,
|
||||||
-10px -12px 0 #6d4c41, -8px -12px 0 #6d4c41, -6px -12px 0 #6d4c41,
|
/* 柱子與牆 */
|
||||||
-4px -12px 0 #6d4c41, -2px -12px 0 #6d4c41, 0px -12px 0 #6d4c41,
|
-4px -2px 0 #8B4513, 4px -2px 0 #8B4513,
|
||||||
2px -12px 0 #6d4c41, 4px -12px 0 #6d4c41, 6px -12px 0 #6d4c41,
|
-4px 0px 0 #8B4513, -2px 0px 0 #FFD700, 0px 0px 0 #FFD700, 2px 0px 0 #FFD700, 4px 0px 0 #8B4513,
|
||||||
8px -12px 0 #6d4c41, 10px -12px 0 #6d4c41,
|
-4px 2px 0 #8B4513, 4px 2px 0 #8B4513,
|
||||||
|
|
||||||
/* 屋簷金邊 */
|
|
||||||
-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,
|
|
||||||
|
|
||||||
/* 底座 */
|
/* 底座 */
|
||||||
-10px 0px 0 #4e342e, -8px 0px 0 #4e342e, -6px 0px 0 #4e342e,
|
-4px 4px 0 #654321, -2px 4px 0 #654321, 0px 4px 0 #654321, 2px 4px 0 #654321, 4px 4px 0 #654321;
|
||||||
-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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<!-- 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,135 +1,144 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { api } from '../services/api';
|
|
||||||
|
|
||||||
export function useEventSystem(petSystem) {
|
export function useEventSystem(petSystem) {
|
||||||
const { stats, state, triggerState } = petSystem;
|
const { stats, state, triggerState } = petSystem;
|
||||||
|
|
||||||
const currentEvent = ref(null);
|
const currentEvent = ref(null);
|
||||||
const eventHistory = ref([]);
|
const eventHistory = ref([]);
|
||||||
const eventPool = ref([]);
|
|
||||||
const eventChance = ref(0.01);
|
|
||||||
|
|
||||||
// --- Event Logic Map ---
|
// --- Event Database ---
|
||||||
// Maps JSON event IDs to complex conditions or custom effects if needed
|
// Categories: 'good', 'bad', 'weird'
|
||||||
// Simple effects (stat changes) are handled automatically
|
const EVENT_DATABASE = [
|
||||||
const LOGIC_MAP = {
|
// --- Good Events ---
|
||||||
'self_clean': {
|
{
|
||||||
|
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--; },
|
||||||
condition: (s) => s.poopCount > 0,
|
condition: (s) => s.poopCount > 0,
|
||||||
customEffect: (s) => { if (s.poopCount > 0) s.poopCount--; }
|
icon: 'pixel-broom'
|
||||||
},
|
},
|
||||||
'dance': {
|
{
|
||||||
condition: (s) => s.happiness > 70
|
id: 'dance',
|
||||||
|
type: 'good',
|
||||||
|
text: '開心地跳舞',
|
||||||
|
effect: (s) => { s.happiness = Math.min(100, s.happiness + 10); },
|
||||||
|
condition: (s) => s.happiness > 70,
|
||||||
|
icon: 'pixel-note'
|
||||||
},
|
},
|
||||||
'night_noise': {
|
{
|
||||||
condition: () => isNight()
|
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'
|
||||||
},
|
},
|
||||||
'overeat': {
|
|
||||||
condition: (s) => s.hunger > 80
|
// --- Bad Events ---
|
||||||
|
{
|
||||||
|
id: 'night_noise',
|
||||||
|
type: 'bad',
|
||||||
|
text: '半夜亂叫',
|
||||||
|
effect: (s) => { s.happiness = Math.max(0, s.happiness - 5); },
|
||||||
|
condition: (s) => isNight(),
|
||||||
|
icon: 'pixel-angry'
|
||||||
},
|
},
|
||||||
'stomach_ache': {
|
{
|
||||||
condition: (s) => s.health < 50
|
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'
|
||||||
},
|
},
|
||||||
'glitch': {
|
{
|
||||||
|
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++; },
|
||||||
condition: () => Math.random() < 0.1,
|
condition: () => Math.random() < 0.1,
|
||||||
customEffect: (s) => { if (s.int > 5) s.int++; }
|
icon: 'pixel-glitch'
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
|
|
||||||
function isNight() {
|
function isNight() {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
return hour >= 22 || hour < 6;
|
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() {
|
function checkEventTriggers() {
|
||||||
if (state.value === 'sleep' || state.value === 'dead') return;
|
if (state.value === 'sleep' || state.value === 'dead') return;
|
||||||
|
|
||||||
// Reload config occasionally? For now assume loaded on init.
|
// 10% chance to trigger an event per check
|
||||||
if (eventPool.value.length === 0) {
|
// Destiny Effect: Luck (福運) - Good events more likely?
|
||||||
loadEventConfig(); // Try to load if empty
|
// Destiny Effect: Spiritual (靈視) - Night events more likely
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.random() < eventChance.value) {
|
if (Math.random() < 0.1) {
|
||||||
triggerRandomEvent();
|
triggerRandomEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerRandomEvent() {
|
function triggerRandomEvent() {
|
||||||
// Filter valid events
|
// Filter valid events
|
||||||
const validEvents = eventPool.value.filter(event => {
|
const validEvents = EVENT_DATABASE.filter(e => e.condition(stats.value));
|
||||||
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;
|
if (validEvents.length === 0) return;
|
||||||
|
|
||||||
// Weighted Random Selection
|
const event = validEvents[Math.floor(Math.random() * validEvents.length)];
|
||||||
const totalWeight = validEvents.reduce((sum, e) => sum + (e.weight || 1), 0);
|
|
||||||
let random = Math.random() * totalWeight;
|
|
||||||
let selectedEvent = validEvents[0];
|
|
||||||
|
|
||||||
for (const event of validEvents) {
|
// Apply effect
|
||||||
random -= (event.weight || 1);
|
event.effect(stats.value);
|
||||||
if (random <= 0) {
|
|
||||||
selectedEvent = event;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyEventEffect(selectedEvent);
|
|
||||||
|
|
||||||
// Set current event for UI
|
// Set current event for UI
|
||||||
currentEvent.value = selectedEvent;
|
currentEvent.value = event;
|
||||||
eventHistory.value.unshift({ ...selectedEvent, time: new Date() });
|
eventHistory.value.unshift({ ...event, time: new Date() });
|
||||||
|
|
||||||
// Auto-clear event after 4 seconds
|
// Auto-clear event after 3 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
currentEvent.value = null;
|
currentEvent.value = null;
|
||||||
}, 4000);
|
}, 4000);
|
||||||
|
|
||||||
console.log('Event Triggered:', selectedEvent.text);
|
console.log('Event Triggered:', event.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 {
|
return {
|
||||||
currentEvent,
|
currentEvent,
|
||||||
eventHistory,
|
eventHistory,
|
||||||
checkEventTriggers,
|
checkEventTriggers,
|
||||||
triggerRandomEvent,
|
triggerRandomEvent
|
||||||
loadEventConfig
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
import { api } from '../services/api';
|
|
||||||
|
|
||||||
export function usePetSystem() {
|
export function usePetSystem() {
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const stage = ref('egg'); // egg, baby, adult
|
const stage = ref('egg'); // egg, baby, adult
|
||||||
const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse
|
const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse
|
||||||
const isLoading = ref(true);
|
|
||||||
const error = ref(null);
|
|
||||||
|
|
||||||
// --- Destiny Data ---
|
// --- Destiny Data ---
|
||||||
const DESTINIES = [
|
const DESTINIES = [
|
||||||
|
|
@ -46,16 +43,6 @@ export function usePetSystem() {
|
||||||
dailyPrayerCount: 0
|
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([
|
const achievements = ref([
|
||||||
{ id: 'newbie', name: '新手飼主', desc: '養育超過 1 天', unlocked: false, icon: '🥚' },
|
{ id: 'newbie', name: '新手飼主', desc: '養育超過 1 天', unlocked: false, icon: '🥚' },
|
||||||
{ id: 'veteran', name: '資深飼主', desc: '養育超過 7 天', unlocked: false, icon: '🏆' },
|
{ id: 'veteran', name: '資深飼主', desc: '養育超過 7 天', unlocked: false, icon: '🏆' },
|
||||||
|
|
@ -71,48 +58,6 @@ export function usePetSystem() {
|
||||||
|
|
||||||
const isCleaning = ref(false);
|
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 ---
|
// --- Actions ---
|
||||||
|
|
||||||
function assignDestiny() {
|
function assignDestiny() {
|
||||||
|
|
@ -131,12 +76,9 @@ export function usePetSystem() {
|
||||||
const picked = pool[Math.floor(Math.random() * pool.length)];
|
const picked = pool[Math.floor(Math.random() * pool.length)];
|
||||||
stats.value.destiny = picked;
|
stats.value.destiny = picked;
|
||||||
console.log('Assigned Destiny:', picked);
|
console.log('Assigned Destiny:', picked);
|
||||||
|
|
||||||
// Sync to API
|
|
||||||
api.updatePetStatus({ stats: { destiny: picked } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function feed() {
|
function feed() {
|
||||||
if (state.value === 'sleep' || state.value === 'dead' || stage.value === 'egg' || isCleaning.value) return false;
|
if (state.value === 'sleep' || state.value === 'dead' || stage.value === 'egg' || isCleaning.value) return false;
|
||||||
|
|
||||||
if (state.value === 'sick' || stats.value.hunger >= 90) {
|
if (state.value === 'sick' || stats.value.hunger >= 90) {
|
||||||
|
|
@ -150,21 +92,12 @@ export function usePetSystem() {
|
||||||
stats.value.hunger = Math.min(100, stats.value.hunger + 20);
|
stats.value.hunger = Math.min(100, stats.value.hunger + 20);
|
||||||
stats.value.weight += 50;
|
stats.value.weight += 50;
|
||||||
|
|
||||||
// Sync to API
|
// Chance to poop after eating (降低機率)
|
||||||
await api.updatePetStatus({
|
// Destiny Effect: Gluttony (暴食) might increase poop chance? Or just hunger decay.
|
||||||
state: 'eating',
|
if (Math.random() < 0.15) { // 從 0.3 降到 0.15
|
||||||
stats: {
|
setTimeout(() => {
|
||||||
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) {
|
if (stats.value.poopCount < 4) {
|
||||||
stats.value.poopCount++;
|
stats.value.poopCount++;
|
||||||
await api.updatePetStatus({ stats: { poopCount: stats.value.poopCount } });
|
|
||||||
}
|
}
|
||||||
}, 4000);
|
}, 4000);
|
||||||
}
|
}
|
||||||
|
|
@ -172,40 +105,24 @@ export function usePetSystem() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function play() {
|
function play() {
|
||||||
if (state.value !== 'idle' || stage.value === 'egg' || isCleaning.value) return false;
|
if (state.value !== 'idle' || stage.value === 'egg' || isCleaning.value) return false;
|
||||||
|
|
||||||
stats.value.happiness = Math.min(100, stats.value.happiness + 15);
|
stats.value.happiness = Math.min(100, stats.value.happiness + 15);
|
||||||
stats.value.weight -= 10; // Exercise burns calories
|
stats.value.weight -= 10; // Exercise burns calories
|
||||||
stats.value.hunger = Math.max(0, stats.value.hunger - 5);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clean() {
|
function clean() {
|
||||||
if (stats.value.poopCount > 0 && !isCleaning.value) {
|
if (stats.value.poopCount > 0 && !isCleaning.value) {
|
||||||
isCleaning.value = true;
|
isCleaning.value = true;
|
||||||
|
|
||||||
// Delay removal for animation
|
// Delay removal for animation
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
stats.value.poopCount = 0;
|
stats.value.poopCount = 0;
|
||||||
stats.value.happiness += 10;
|
stats.value.happiness += 10;
|
||||||
isCleaning.value = false;
|
isCleaning.value = false;
|
||||||
|
|
||||||
await api.updatePetStatus({
|
|
||||||
stats: {
|
|
||||||
poopCount: 0,
|
|
||||||
happiness: stats.value.happiness
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 2000); // 2 seconds flush animation
|
}, 2000); // 2 seconds flush animation
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -213,7 +130,7 @@ export function usePetSystem() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sleep() {
|
function sleep() {
|
||||||
if (isCleaning.value) return;
|
if (isCleaning.value) return;
|
||||||
|
|
||||||
if (state.value === 'idle') {
|
if (state.value === 'idle') {
|
||||||
|
|
@ -221,34 +138,27 @@ export function usePetSystem() {
|
||||||
} else if (state.value === 'sleep') {
|
} else if (state.value === 'sleep') {
|
||||||
state.value = 'idle'; // Wake up
|
state.value = 'idle'; // Wake up
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.updatePetStatus({ state: state.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Game Loop ---
|
// --- Game Loop ---
|
||||||
function tick() {
|
function tick() {
|
||||||
if (state.value === 'dead' || stage.value === 'egg') return;
|
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
|
// Decrease stats naturally
|
||||||
let hungerDecay = rates.hungerDecay;
|
// Destiny Effect: Gluttony (暴食) - Hunger decreases faster (+30%)
|
||||||
|
let hungerDecay = 0.05;
|
||||||
if (stats.value.destiny?.id === 'gluttony') {
|
if (stats.value.destiny?.id === 'gluttony') {
|
||||||
hungerDecay *= 1.3;
|
hungerDecay *= 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
let happinessDecay = rates.happinessDecay;
|
// Destiny Effect: Playful (愛玩) - Happiness decreases faster
|
||||||
|
let happinessDecay = 0.08;
|
||||||
if (stats.value.destiny?.id === 'playful') {
|
if (stats.value.destiny?.id === 'playful') {
|
||||||
happinessDecay *= 1.2; // Faster decay
|
happinessDecay *= 1.2; // Faster decay
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destiny Effect: DEX (敏捷) - Hunger decreases slower
|
// Destiny Effect: DEX (敏捷) - Hunger decreases slower
|
||||||
|
// DEX 10 = -10% decay, DEX 50 = -50% decay
|
||||||
if (stats.value.dex > 0) {
|
if (stats.value.dex > 0) {
|
||||||
const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction
|
const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction
|
||||||
hungerDecay *= (1 - reduction);
|
hungerDecay *= (1 - reduction);
|
||||||
|
|
@ -263,28 +173,34 @@ export function usePetSystem() {
|
||||||
stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
|
stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random poop generation
|
// Random poop generation (更低的機率:約 0.5% per tick)
|
||||||
if (state.value !== 'sleep' && Math.random() < rates.poopChance && stats.value.poopCount < 4 && !isCleaning.value) {
|
// 平均約每 200 ticks = 10 分鐘拉一次
|
||||||
|
if (state.value !== 'sleep' && Math.random() < 0.005 && stats.value.poopCount < 4 && !isCleaning.value) {
|
||||||
stats.value.poopCount++;
|
stats.value.poopCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health Logic
|
// Health Logic (更溫和的健康下降)
|
||||||
|
// 便便影響健康:每個便便每 tick -0.1 health
|
||||||
if (stats.value.poopCount > 0) {
|
if (stats.value.poopCount > 0) {
|
||||||
stats.value.health = Math.max(0, stats.value.health - (0.1 * stats.value.poopCount));
|
stats.value.health = Math.max(0, stats.value.health - (0.1 * stats.value.poopCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 飢餓影響健康:飢餓值低於 20 時開始影響健康
|
||||||
if (stats.value.hunger < 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);
|
stats.value.health = Math.max(0, stats.value.health - hungerPenalty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 不開心影響健康:快樂值低於 20 時開始影響健康(較輕微)
|
||||||
if (stats.value.happiness < 20) {
|
if (stats.value.happiness < 20) {
|
||||||
const happinessPenalty = (20 - stats.value.happiness) * 0.01;
|
const happinessPenalty = (20 - stats.value.happiness) * 0.01;
|
||||||
stats.value.health = Math.max(0, stats.value.health - happinessPenalty);
|
stats.value.health = Math.max(0, stats.value.health - happinessPenalty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sickness Check
|
// Sickness Check (更低的生病機率)
|
||||||
let sickChance = rates.sickChance;
|
// Destiny Effect: Purification (淨化) - Sickness chance -20%
|
||||||
|
// Deity Buff: 媽祖 - Sickness chance -15%
|
||||||
|
let sickChance = 0.1;
|
||||||
if (stats.value.destiny?.id === 'purification') {
|
if (stats.value.destiny?.id === 'purification') {
|
||||||
sickChance *= 0.8;
|
sickChance *= 0.8;
|
||||||
}
|
}
|
||||||
|
|
@ -295,17 +211,21 @@ export function usePetSystem() {
|
||||||
if (stats.value.health < 30 && state.value !== 'sick') {
|
if (stats.value.health < 30 && state.value !== 'sick') {
|
||||||
if (Math.random() < sickChance) {
|
if (Math.random() < sickChance) {
|
||||||
state.value = 'sick';
|
state.value = 'sick';
|
||||||
api.updatePetStatus({ state: 'sick' });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health Recovery
|
// Health Recovery (健康值可以緩慢恢復)
|
||||||
|
// 如果沒有便便、飢餓值和快樂值都高,健康值會緩慢恢復
|
||||||
let healthRecovery = 0.05;
|
let healthRecovery = 0.05;
|
||||||
|
|
||||||
|
// Deity Buff: 觀音 - Health 回復 +20%
|
||||||
if (stats.value.currentDeity === 'guanyin' && stats.value.deityFavors?.guanyin > 0) {
|
if (stats.value.currentDeity === 'guanyin' && stats.value.deityFavors?.guanyin > 0) {
|
||||||
healthRecovery *= 1.2;
|
healthRecovery *= 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deity Buff: 月老 - Happiness 回復 +25%
|
||||||
if (stats.value.currentDeity === 'matchmaker' && stats.value.deityFavors?.matchmaker > 0) {
|
if (stats.value.currentDeity === 'matchmaker' && stats.value.deityFavors?.matchmaker > 0) {
|
||||||
|
// Apply to happiness decay reduction (slower decay = faster recovery)
|
||||||
happinessDecay *= 0.75;
|
happinessDecay *= 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,15 +233,17 @@ export function usePetSystem() {
|
||||||
stats.value.health = Math.min(100, stats.value.health + healthRecovery);
|
stats.value.health = Math.min(100, stats.value.health + healthRecovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Death Check (移除死亡機制,依照之前的討論)
|
||||||
|
// if (stats.value.health === 0) {
|
||||||
|
// state.value = 'dead';
|
||||||
|
// }
|
||||||
|
|
||||||
// Evolution / Growth
|
// Evolution / Growth
|
||||||
tickCount++;
|
tickCount++;
|
||||||
if (tickCount >= TICKS_PER_DAY) {
|
if (tickCount >= TICKS_PER_DAY) {
|
||||||
stats.value.age++;
|
stats.value.age++;
|
||||||
tickCount = 0;
|
tickCount = 0;
|
||||||
checkEvolution();
|
checkEvolution();
|
||||||
|
|
||||||
// Sync stats periodically (e.g., every "day")
|
|
||||||
api.updatePetStatus({ stats: stats.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAchievements();
|
checkAchievements();
|
||||||
|
|
@ -354,20 +276,14 @@ export function usePetSystem() {
|
||||||
triggerState('happy', 2000);
|
triggerState('happy', 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkEvolution() {
|
function checkEvolution() {
|
||||||
// Simple evolution logic
|
// Simple evolution logic
|
||||||
let evolved = false;
|
|
||||||
if (stage.value === 'baby' && stats.value.age >= 3) {
|
if (stage.value === 'baby' && stats.value.age >= 3) {
|
||||||
stage.value = 'child';
|
stage.value = 'child';
|
||||||
evolved = true;
|
triggerState('happy', 2000); // Celebrate
|
||||||
} else if (stage.value === 'child' && stats.value.age >= 7) {
|
} else if (stage.value === 'child' && stats.value.age >= 7) {
|
||||||
stage.value = 'adult';
|
stage.value = 'adult';
|
||||||
evolved = true;
|
triggerState('happy', 2000);
|
||||||
}
|
|
||||||
|
|
||||||
if (evolved) {
|
|
||||||
triggerState('happy', 2000); // Celebrate
|
|
||||||
await api.updatePetStatus({ stage: stage.value });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -375,18 +291,18 @@ export function usePetSystem() {
|
||||||
function triggerState(tempState, duration) {
|
function triggerState(tempState, duration) {
|
||||||
const previousState = state.value;
|
const previousState = state.value;
|
||||||
state.value = tempState;
|
state.value = tempState;
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
if (state.value === tempState) { // Only revert if state hasn't changed again
|
if (state.value === tempState) { // Only revert if state hasn't changed again
|
||||||
state.value = previousState === 'sleep' ? 'idle' : 'idle';
|
state.value = previousState === 'sleep' ? 'idle' : 'idle';
|
||||||
// Sync state revert
|
|
||||||
await api.updatePetStatus({ state: state.value });
|
|
||||||
}
|
}
|
||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hatchEgg() {
|
function hatchEgg() {
|
||||||
if (stage.value === 'egg') {
|
if (stage.value === 'egg') {
|
||||||
stage.value = 'adult'; // Skip to adult for demo
|
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';
|
||||||
state.value = 'idle';
|
state.value = 'idle';
|
||||||
stats.value.hunger = 50;
|
stats.value.hunger = 50;
|
||||||
stats.value.happiness = 50;
|
stats.value.happiness = 50;
|
||||||
|
|
@ -397,22 +313,32 @@ export function usePetSystem() {
|
||||||
assignDestiny();
|
assignDestiny();
|
||||||
|
|
||||||
isCleaning.value = false;
|
isCleaning.value = false;
|
||||||
|
|
||||||
await api.updatePetStatus({
|
|
||||||
stage: stage.value,
|
|
||||||
state: state.value,
|
|
||||||
stats: stats.value
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reset() {
|
function reset() {
|
||||||
await api.resetGame();
|
stage.value = 'egg';
|
||||||
await initGame(); // Reload initial data
|
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
|
||||||
|
};
|
||||||
tickCount = 0;
|
tickCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resurrect() {
|
function resurrect() {
|
||||||
if (state.value !== 'dead') return;
|
if (state.value !== 'dead') return;
|
||||||
|
|
||||||
state.value = 'idle';
|
state.value = 'idle';
|
||||||
|
|
@ -420,45 +346,39 @@ export function usePetSystem() {
|
||||||
stats.value.happiness = 50;
|
stats.value.happiness = 50;
|
||||||
stats.value.hunger = 50;
|
stats.value.hunger = 50;
|
||||||
|
|
||||||
|
// Penalty or Ghost Buff?
|
||||||
|
// For now just a console log, maybe visual effect later
|
||||||
console.log('Pet Resurrected!');
|
console.log('Pet Resurrected!');
|
||||||
|
|
||||||
await api.updatePetStatus({
|
|
||||||
state: state.value,
|
|
||||||
stats: stats.value
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reincarnate() {
|
function reincarnate() {
|
||||||
// Inherit logic
|
// Inherit logic
|
||||||
const prevDestiny = stats.value.destiny;
|
const prevDestiny = stats.value.destiny;
|
||||||
const prevFavor = stats.value.deityFavor;
|
const prevFavor = stats.value.deityFavor;
|
||||||
const nextGen = (stats.value.generation || 1) + 1;
|
const nextGen = (stats.value.generation || 1) + 1;
|
||||||
|
|
||||||
// Reset everything
|
// Reset everything
|
||||||
await api.resetGame();
|
reset();
|
||||||
|
|
||||||
// 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
|
// Apply Inheritance
|
||||||
stats.value.generation = nextGen;
|
stats.value.generation = nextGen;
|
||||||
|
|
||||||
|
// 20% Favor inheritance
|
||||||
stats.value.deityFavor = Math.floor(prevFavor * 0.2);
|
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) {
|
if (prevDestiny && prevDestiny.rarity === 2) {
|
||||||
stats.value.destiny = prevDestiny;
|
stats.value.destiny = prevDestiny;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Pet Reincarnated to Gen', nextGen);
|
console.log('Pet Reincarnated to Gen', nextGen);
|
||||||
|
|
||||||
await api.updatePetStatus({ stats: stats.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Lifecycle ---
|
// --- Lifecycle ---
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// initGame is called manually or by parent
|
gameLoopId = setInterval(tick, TICK_RATE);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
@ -470,9 +390,6 @@ export function usePetSystem() {
|
||||||
state,
|
state,
|
||||||
stats,
|
stats,
|
||||||
isCleaning,
|
isCleaning,
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
initGame,
|
|
||||||
feed,
|
feed,
|
||||||
play,
|
play,
|
||||||
clean,
|
clean,
|
||||||
|
|
@ -481,7 +398,7 @@ export function usePetSystem() {
|
||||||
reset,
|
reset,
|
||||||
achievements,
|
achievements,
|
||||||
unlockAllAchievements,
|
unlockAllAchievements,
|
||||||
assignDestiny,
|
assignDestiny, // Export for debug if needed
|
||||||
resurrect,
|
resurrect,
|
||||||
reincarnate
|
reincarnate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
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 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,273 +0,0 @@
|
||||||
// 模擬資料庫 - 遊戲初始狀態
|
|
||||||
// 這裡存放所有的遊戲資料,模擬後端資料庫結構
|
|
||||||
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