good version
This commit is contained in:
parent
1c068b1672
commit
53bdcb8a9d
24
src/App.vue
24
src/App.vue
|
|
@ -5,6 +5,7 @@ import DeviceScreen from './components/DeviceScreen.vue';
|
|||
import PetGame from './components/PetGame.vue';
|
||||
import Menu from './components/Menu.vue';
|
||||
import { usePetSystem } from './composables/usePetSystem';
|
||||
import { useEventSystem } from './composables/useEventSystem';
|
||||
|
||||
const currentScreen = ref('game');
|
||||
const petGameRef = ref(null);
|
||||
|
|
@ -12,6 +13,7 @@ const showStats = ref(false); // Stats visibility
|
|||
const debugAction = ref(null); // For passing debug commands to PetGame
|
||||
|
||||
// Initialize Pet System
|
||||
const petSystem = usePetSystem();
|
||||
const {
|
||||
stage,
|
||||
state,
|
||||
|
|
@ -24,8 +26,19 @@ const {
|
|||
hatchEgg,
|
||||
reset,
|
||||
achievements,
|
||||
unlockAllAchievements
|
||||
} = usePetSystem();
|
||||
unlockAllAchievements,
|
||||
resurrect,
|
||||
reincarnate
|
||||
} = petSystem;
|
||||
|
||||
// Initialize Event System
|
||||
const { currentEvent, checkEventTriggers, triggerRandomEvent } = useEventSystem(petSystem);
|
||||
|
||||
// Start Event Loop
|
||||
setInterval(() => {
|
||||
checkEventTriggers();
|
||||
}, 10000); // Check every 10 seconds
|
||||
|
||||
|
||||
// Handle Action Menu Events
|
||||
function handleAction(action) {
|
||||
|
|
@ -88,6 +101,12 @@ function handleAction(action) {
|
|||
console.log('求籤功能');
|
||||
// TODO: 實作求籤邏輯
|
||||
break;
|
||||
case 'resurrect':
|
||||
resurrect();
|
||||
break;
|
||||
case 'reincarnate':
|
||||
reincarnate();
|
||||
break;
|
||||
default:
|
||||
console.log('Action not implemented:', action);
|
||||
}
|
||||
|
|
@ -117,6 +136,7 @@ function triggerDebugAction(action, payload = null) {
|
|||
:showStats="showStats"
|
||||
:debugAction="debugAction"
|
||||
:achievements="achievements"
|
||||
:currentEvent="currentEvent"
|
||||
@update:state="state = $event"
|
||||
@action="handleAction"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</template>
|
||||
|
||||
<!-- 求籤模式 -->
|
||||
<template v-else>
|
||||
<template v-else-if="mode === 'fortune'">
|
||||
<!-- 失敗 (非聖筊) -->
|
||||
<button v-if="resultType !== 'saint'" class="pixel-btn close-btn" @click="handleRetryFortune">重新求籤</button>
|
||||
|
||||
|
|
@ -45,6 +45,11 @@
|
|||
<button v-else class="pixel-btn retry-btn" @click="$emit('finish-fortune')">查看籤詩</button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 招魂模式 -->
|
||||
<template v-else-if="mode === 'resurrect'">
|
||||
<button class="pixel-btn retry-btn" @click="$emit('resurrect-confirm')">確認</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -100,8 +100,21 @@
|
|||
<!-- 生病骷髏頭 -->
|
||||
<div class="sick-icon" :style="iconStyle" v-show="state === 'sick'">💀</div>
|
||||
|
||||
<!-- 死亡墓碑 -->
|
||||
<div class="tombstone" v-show="state === 'dead'"></div>
|
||||
<!-- 死亡墓碑 & 選單 -->
|
||||
<div class="death-screen" v-show="state === 'dead'">
|
||||
<div class="tombstone"></div>
|
||||
<div class="death-menu">
|
||||
<div class="death-title">寵物已死亡</div>
|
||||
<div class="death-actions">
|
||||
<button class="action-btn" @click="handleResurrectAttempt">
|
||||
🕯️ 招魂 (擲筊)
|
||||
</button>
|
||||
<button class="action-btn" @click="handleReincarnate">
|
||||
🥚 輪迴轉世
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 便便 (Poop) - Scattered on sides -->
|
||||
<div
|
||||
|
|
@ -151,6 +164,17 @@
|
|||
>
|
||||
<div :class="eventAnimation.iconClass"></div>
|
||||
</div>
|
||||
|
||||
<!-- Current Event Bubble -->
|
||||
<div
|
||||
v-if="currentEvent"
|
||||
class="event-bubble"
|
||||
:class="currentEvent.type"
|
||||
:style="{ left: (petX + width/2) + 'px', top: (petY - 70) + 'px' }"
|
||||
>
|
||||
<div class="event-icon" :class="currentEvent.icon"></div>
|
||||
<div class="event-text">{{ currentEvent.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prayer Menu (覆蓋整個遊戲區域) -->
|
||||
|
|
@ -169,6 +193,7 @@
|
|||
@result="handleJiaobeiResult"
|
||||
@retry-fortune="handleRetryFortune"
|
||||
@finish-fortune="handleFinishFortune"
|
||||
@resurrect-confirm="handleResurrectConfirm"
|
||||
/>
|
||||
|
||||
<!-- Fortune Stick Animation -->
|
||||
|
|
@ -195,6 +220,12 @@
|
|||
:hunger="stats?.hunger || 100"
|
||||
:happiness="stats?.happiness || 100"
|
||||
:health="stats?.health || 100"
|
||||
:str="stats?.str || 0"
|
||||
:int="stats?.int || 0"
|
||||
:dex="stats?.dex || 0"
|
||||
:generation="stats?.generation || 1"
|
||||
:deityFavor="stats?.deityFavor || 0"
|
||||
:destiny="stats?.destiny || null"
|
||||
:achievements="achievements"
|
||||
@close="showPetInfo = false"
|
||||
/>
|
||||
|
|
@ -301,6 +332,10 @@ const props = defineProps({
|
|||
achievements: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentEvent: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -315,6 +350,8 @@ const showFortuneResult = ref(false);
|
|||
const currentLotData = ref(null);
|
||||
const currentLotNumber = ref(null);
|
||||
const consecutiveSaintCount = ref(0);
|
||||
const lastJiaobeiResult = ref(null); // 記錄最後的擲筊結果用於招魂確認
|
||||
|
||||
const showPetInfo = ref(false);
|
||||
const showInventory = ref(false);
|
||||
const showPlayMenu = ref(false);
|
||||
|
|
@ -416,6 +453,7 @@ function handleTrainingAttack() {
|
|||
|
||||
function handleGameComplete(won) {
|
||||
console.log('Game completed, won:', won);
|
||||
const gameType = currentGame.value; // Capture before clearing
|
||||
currentGame.value = '';
|
||||
|
||||
if (won) {
|
||||
|
|
@ -434,6 +472,20 @@ function handleGameComplete(won) {
|
|||
// 增加一些數值作為獎勵
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 10);
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - 5); // 運動會餓
|
||||
|
||||
// V2 Stats Growth
|
||||
let statGain = 1;
|
||||
// Destiny Effect: Diligence (勤奮) - +20% rewards (rounded up to +1 extra for small values)
|
||||
if (stats.value.destiny?.id === 'diligence') {
|
||||
statGain += 1;
|
||||
}
|
||||
|
||||
if (gameType === 'training') {
|
||||
stats.value.str = (stats.value.str || 0) + statGain;
|
||||
// Training also gives Deity Favor slightly? Maybe not.
|
||||
} else if (gameType === 'guessing') {
|
||||
stats.value.int = (stats.value.int || 0) + statGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +512,13 @@ function handleBallGameComplete(won) {
|
|||
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 15);
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - 10);
|
||||
|
||||
// V2 Stats Growth: DEX
|
||||
let statGain = 1;
|
||||
if (stats.value.destiny?.id === 'diligence') {
|
||||
statGain += 1;
|
||||
}
|
||||
stats.value.dex = (stats.value.dex || 0) + statGain;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -480,16 +539,64 @@ function handleFortuneStickClose() {
|
|||
consecutiveSaintCount.value = 0;
|
||||
}
|
||||
|
||||
function handleResurrectAttempt() {
|
||||
// 招魂:需要擲筊
|
||||
fortuneMode.value = 'resurrect';
|
||||
showJiaobeiAnimation.value = true;
|
||||
consecutiveSaintCount.value = 0;
|
||||
}
|
||||
|
||||
function handleReincarnate() {
|
||||
emit('action', 'reincarnate');
|
||||
}
|
||||
|
||||
function handleJiaobeiResult(type) {
|
||||
if (fortuneMode.value === 'fortune') {
|
||||
if (type === 'saint') {
|
||||
consecutiveSaintCount.value++;
|
||||
} else {
|
||||
// 如果不是聖筊,計數歸零(邏輯上失敗了,需要重新求籤)
|
||||
// V2: Pet Reaction
|
||||
triggerState('happy', 1000);
|
||||
eventAnimation.value = { type: 'float-up', iconClass: 'pixel-heart' };
|
||||
setTimeout(() => eventAnimation.value = null, 1000);
|
||||
} else if (type === 'laugh') {
|
||||
// 笑筊
|
||||
consecutiveSaintCount.value = 0;
|
||||
triggerState('refuse', 1000); // Confused
|
||||
eventAnimation.value = { type: 'float-up', iconClass: 'pixel-question' };
|
||||
setTimeout(() => eventAnimation.value = null, 1000);
|
||||
} else {
|
||||
// 陰筊
|
||||
consecutiveSaintCount.value = 0;
|
||||
triggerState('sad', 1000); // Scared
|
||||
eventAnimation.value = { type: 'float-up', iconClass: 'pixel-skull' };
|
||||
setTimeout(() => eventAnimation.value = null, 1000);
|
||||
}
|
||||
} else if (fortuneMode.value === 'resurrect') {
|
||||
// 招魂模式:記錄結果,等待用戶點擊"確認"
|
||||
lastJiaobeiResult.value = type;
|
||||
}
|
||||
}
|
||||
|
||||
function handleResurrectConfirm() {
|
||||
// 用戶點擊招魂的"確認"按鈕
|
||||
const result = lastJiaobeiResult.value;
|
||||
|
||||
showJiaobeiAnimation.value = false;
|
||||
fortuneMode.value = 'normal';
|
||||
lastJiaobeiResult.value = null;
|
||||
|
||||
if (result === 'saint') {
|
||||
// 聖筊 - 復活
|
||||
emit('action', 'resurrect');
|
||||
eventAnimation.value = { type: 'float-up', iconClass: 'pixel-heart' };
|
||||
setTimeout(() => eventAnimation.value = null, 2000);
|
||||
} else {
|
||||
// 非聖筊 - 直接輪迴
|
||||
emit('action', 'reincarnate');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function handleRetryFortune() {
|
||||
// 重新開始搖籤
|
||||
|
|
@ -505,6 +612,39 @@ function handleFinishFortune() {
|
|||
currentLotData.value = lot;
|
||||
showJiaobeiAnimation.value = false;
|
||||
showFortuneResult.value = true;
|
||||
|
||||
// --- V2 Fortune Impact ---
|
||||
// Increase Deity Favor for completing the ritual
|
||||
stats.value.deityFavor = (stats.value.deityFavor || 0) + 1;
|
||||
|
||||
const grade = lot.grade; // e.g., "上上", "上吉", "中平", "下下"
|
||||
|
||||
if (grade.includes('上上')) {
|
||||
triggerState('happy', 3000);
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 20);
|
||||
stats.value.health = Math.min(100, stats.value.health + 10);
|
||||
eventAnimation.value = { type: 'float-up', iconClass: 'pixel-heart' };
|
||||
} else if (grade.includes('上') || grade.includes('大吉')) {
|
||||
triggerState('happy', 2000);
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 10);
|
||||
} else if (grade.includes('下下')) {
|
||||
triggerState('sad', 3000);
|
||||
// Trigger Bad Event
|
||||
if (triggerRandomEvent) {
|
||||
// Force a bad event or just increase risk
|
||||
// For now, let's just lower stats
|
||||
stats.value.happiness = Math.max(0, stats.value.happiness - 10);
|
||||
}
|
||||
} else if (grade.includes('下')) {
|
||||
triggerState('sad', 2000);
|
||||
stats.value.health = Math.max(0, stats.value.health - 5);
|
||||
}
|
||||
|
||||
// Destiny Effect: Luck (福運) - Bonus happiness
|
||||
if (stats.value.destiny?.id === 'luck' && (grade.includes('上') || grade.includes('吉'))) {
|
||||
stats.value.happiness = Math.min(100, stats.value.happiness + 5);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error('Lot not found:', currentLotNumber.value);
|
||||
handleJiaobeiClose();
|
||||
|
|
@ -1690,6 +1830,69 @@ defineExpose({
|
|||
50% { transform: translate(-50%, -50%) translateY(-4px); }
|
||||
}
|
||||
|
||||
/* Death Screen */
|
||||
.death-screen {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.death-menu {
|
||||
margin-top: -30px; /* Overlay on top of tombstone */
|
||||
background: rgba(224, 224, 224, 0.95);
|
||||
border: 4px solid #555;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 0 #333;
|
||||
max-width: 180px;
|
||||
width: 90%;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.death-title {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.death-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
padding: 6px 10px;
|
||||
background: #fff;
|
||||
border: 2px solid #888;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: all 0.1s;
|
||||
white-space: normal;
|
||||
line-height: 1.2;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(2px);
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1773,6 +1976,51 @@ defineExpose({
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
.pixel-note {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
transform: scale(2);
|
||||
color: #ff1493;
|
||||
box-shadow:
|
||||
-2px -2px, -1px -2px, 1px -2px, 2px -2px,
|
||||
-3px -1px, -2px -1px, -1px -1px, 0px -1px, 1px -1px, 2px -1px, 3px -1px,
|
||||
-3px 0px, -2px 0px, -1px 0px, 0px 0px, 1px 0px, 2px 0px, 3px 0px,
|
||||
-2px 1px, -1px 1px, 0px 1px, 1px 1px, 2px 1px,
|
||||
-1px 2px, 0px 2px, 1px 2px,
|
||||
0px 3px;
|
||||
}
|
||||
|
||||
.pixel-question {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
transform: scale(2);
|
||||
color: #9C27B0;
|
||||
box-shadow:
|
||||
-1px -3px, 0px -3px, 1px -3px,
|
||||
-2px -2px, 2px -2px,
|
||||
2px -1px,
|
||||
1px 0px,
|
||||
0px 1px,
|
||||
0px 3px;
|
||||
}
|
||||
|
||||
.pixel-skull {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
transform: scale(2);
|
||||
color: #555;
|
||||
box-shadow:
|
||||
-2px -3px, -1px -3px, 0px -3px, 1px -3px, 2px -3px,
|
||||
-3px -2px, 3px -2px,
|
||||
-3px -1px, -1px -1px, 1px -1px, 3px -1px,
|
||||
-3px 0px, 3px 0px,
|
||||
-2px 1px, -1px 1px, 0px 1px, 1px 1px, 2px 1px,
|
||||
-1px 2px, 1px 2px;
|
||||
}
|
||||
|
||||
.pixel-toy {
|
||||
box-shadow:
|
||||
-1px -2px #32cd32, 0px -2px #32cd32, 1px -2px #32cd32,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,43 @@
|
|||
|
||||
<div class="info-divider"></div>
|
||||
|
||||
<!-- Destiny -->
|
||||
<div class="info-item destiny-item" v-if="destiny">
|
||||
<span class="label">命格</span>
|
||||
<div class="destiny-content">
|
||||
<span class="value destiny-name">{{ destiny.name }}</span>
|
||||
<span class="destiny-desc">{{ destiny.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item" v-else>
|
||||
<span class="label">命格</span>
|
||||
<span class="value">???</span>
|
||||
</div>
|
||||
|
||||
<div class="info-divider"></div>
|
||||
|
||||
<!-- V2 Stats -->
|
||||
<div class="info-item">
|
||||
<span class="label">力量 (STR)</span>
|
||||
<span class="value">{{ str }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">智力 (INT)</span>
|
||||
<span class="value">{{ int }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">敏捷 (DEX)</span>
|
||||
<span class="value">{{ dex }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">世代</span>
|
||||
<span class="value">第 {{ generation }} 代</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">神明好感</span>
|
||||
<span class="value">{{ deityFavor }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<span class="label">HP</span>
|
||||
<span class="value">{{ baseStats.hp }}</span>
|
||||
|
|
@ -170,6 +207,13 @@ const props = defineProps({
|
|||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// v2 Stats
|
||||
str: { type: Number, default: 0 },
|
||||
int: { type: Number, default: 0 },
|
||||
dex: { type: Number, default: 0 },
|
||||
generation: { type: Number, default: 1 },
|
||||
deityFavor: { type: Number, default: 0 },
|
||||
destiny: { type: Object, default: null },
|
||||
achievements: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
|
|
@ -532,6 +576,31 @@ const weight = computed(() => {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.destiny-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.destiny-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.destiny-name {
|
||||
color: #d32f2f; /* Red for emphasis */
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.destiny-desc {
|
||||
font-family: 'DotGothic16', monospace;
|
||||
font-size: 10px;
|
||||
color: #665544;
|
||||
}
|
||||
|
||||
/* Achievements */
|
||||
.achievements-view {
|
||||
padding: 12px;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
export function useEventSystem(petSystem) {
|
||||
const { stats, state, triggerState } = petSystem;
|
||||
|
||||
const currentEvent = ref(null);
|
||||
const eventHistory = ref([]);
|
||||
|
||||
// --- Event Database ---
|
||||
// Categories: 'good', 'bad', 'weird'
|
||||
const EVENT_DATABASE = [
|
||||
// --- Good Events ---
|
||||
{
|
||||
id: 'found_charm',
|
||||
type: 'good',
|
||||
text: '撿到小符',
|
||||
effect: (s) => { s.happiness = Math.min(100, s.happiness + 5); },
|
||||
condition: () => true,
|
||||
icon: 'pixel-charm'
|
||||
},
|
||||
{
|
||||
id: 'self_clean',
|
||||
type: 'good',
|
||||
text: '自己掃地',
|
||||
effect: (s) => { if (s.poopCount > 0) s.poopCount--; },
|
||||
condition: (s) => s.poopCount > 0,
|
||||
icon: 'pixel-broom'
|
||||
},
|
||||
{
|
||||
id: 'dance',
|
||||
type: 'good',
|
||||
text: '開心地跳舞',
|
||||
effect: (s) => { s.happiness = Math.min(100, s.happiness + 10); },
|
||||
condition: (s) => s.happiness > 70,
|
||||
icon: 'pixel-note'
|
||||
},
|
||||
{
|
||||
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'
|
||||
},
|
||||
|
||||
// --- Bad Events ---
|
||||
{
|
||||
id: 'night_noise',
|
||||
type: 'bad',
|
||||
text: '半夜亂叫',
|
||||
effect: (s) => { s.happiness = Math.max(0, s.happiness - 5); },
|
||||
condition: (s) => isNight(),
|
||||
icon: 'pixel-angry'
|
||||
},
|
||||
{
|
||||
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'
|
||||
},
|
||||
{
|
||||
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,
|
||||
icon: 'pixel-glitch'
|
||||
}
|
||||
];
|
||||
|
||||
function isNight() {
|
||||
const hour = new Date().getHours();
|
||||
return hour >= 22 || hour < 6;
|
||||
}
|
||||
|
||||
function checkEventTriggers() {
|
||||
if (state.value === 'sleep' || state.value === 'dead') return;
|
||||
|
||||
// 10% chance to trigger an event per check
|
||||
// Destiny Effect: Luck (福運) - Good events more likely?
|
||||
// Destiny Effect: Spiritual (靈視) - Night events more likely
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
triggerRandomEvent();
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRandomEvent() {
|
||||
// Filter valid events
|
||||
const validEvents = EVENT_DATABASE.filter(e => e.condition(stats.value));
|
||||
if (validEvents.length === 0) return;
|
||||
|
||||
const event = validEvents[Math.floor(Math.random() * validEvents.length)];
|
||||
|
||||
// Apply effect
|
||||
event.effect(stats.value);
|
||||
|
||||
// Set current event for UI
|
||||
currentEvent.value = event;
|
||||
eventHistory.value.unshift({ ...event, time: new Date() });
|
||||
|
||||
// Auto-clear event after 3 seconds
|
||||
setTimeout(() => {
|
||||
currentEvent.value = null;
|
||||
}, 4000);
|
||||
|
||||
console.log('Event Triggered:', event.text);
|
||||
}
|
||||
|
||||
return {
|
||||
currentEvent,
|
||||
eventHistory,
|
||||
checkEventTriggers,
|
||||
triggerRandomEvent
|
||||
};
|
||||
}
|
||||
|
|
@ -5,6 +5,17 @@ export function usePetSystem() {
|
|||
const stage = ref('egg'); // egg, baby, adult
|
||||
const state = ref('idle'); // idle, sleep, eating, sick, dead, refuse
|
||||
|
||||
// --- Destiny Data ---
|
||||
const DESTINIES = [
|
||||
{ id: 'luck', name: '福運', description: '籤詩好籤率 +10%', rarity: 1 },
|
||||
{ id: 'diligence', name: '勤奮', description: '訓練遊戲獎勵 +20%', rarity: 1 },
|
||||
{ id: 'gluttony', name: '暴食', description: '飢餓下降速度 +30%', rarity: 1 },
|
||||
{ id: 'playful', name: '愛玩', description: 'Happiness 更快下降/更快上升', rarity: 1 },
|
||||
{ id: 'purification', name: '淨化', description: '生病機率 -20%', rarity: 1 },
|
||||
{ id: 'thirdeye', name: '天眼', description: '擲筊出聖筊機率微升', rarity: 2 },
|
||||
{ id: 'medium', name: '冥感', description: '死亡後招魂成功率提升', rarity: 2 }
|
||||
];
|
||||
|
||||
// --- Stats ---
|
||||
const stats = ref({
|
||||
hunger: 100, // 0-100 (0 = Starving)
|
||||
|
|
@ -12,7 +23,14 @@ export function usePetSystem() {
|
|||
health: 100, // 0-100 (0 = Sick risk)
|
||||
weight: 500, // grams
|
||||
age: 1, // days (start at day 1)
|
||||
poopCount: 0 // Number of poops on screen
|
||||
poopCount: 0, // Number of poops on screen
|
||||
// v2 Stats
|
||||
str: 0, // 力量 (Fireball Game)
|
||||
int: 0, // 智力 (Guessing Game)
|
||||
dex: 0, // 敏捷 (Catch Ball)
|
||||
generation: 1, // 輪迴世代
|
||||
deityFavor: 0, // 神明好感度
|
||||
destiny: null // 天生命格 (Object)
|
||||
});
|
||||
|
||||
const achievements = ref([
|
||||
|
|
@ -32,6 +50,24 @@ export function usePetSystem() {
|
|||
|
||||
// --- Actions ---
|
||||
|
||||
function assignDestiny() {
|
||||
// Simple weighted random or just random for now
|
||||
// Rarity 2 has lower chance
|
||||
const roll = Math.random();
|
||||
let pool = DESTINIES;
|
||||
|
||||
// 20% chance for rare destiny
|
||||
if (roll < 0.2) {
|
||||
pool = DESTINIES.filter(d => d.rarity === 2);
|
||||
} else {
|
||||
pool = DESTINIES.filter(d => d.rarity === 1);
|
||||
}
|
||||
|
||||
const picked = pool[Math.floor(Math.random() * pool.length)];
|
||||
stats.value.destiny = picked;
|
||||
console.log('Assigned Destiny:', picked);
|
||||
}
|
||||
|
||||
function feed() {
|
||||
if (state.value === 'sleep' || state.value === 'dead' || stage.value === 'egg' || isCleaning.value) return false;
|
||||
|
||||
|
|
@ -47,6 +83,7 @@ export function usePetSystem() {
|
|||
stats.value.weight += 50;
|
||||
|
||||
// Chance to poop after eating (降低機率)
|
||||
// Destiny Effect: Gluttony (暴食) might increase poop chance? Or just hunger decay.
|
||||
if (Math.random() < 0.15) { // 從 0.3 降到 0.15
|
||||
setTimeout(() => {
|
||||
if (stats.value.poopCount < 4) {
|
||||
|
|
@ -98,18 +135,32 @@ export function usePetSystem() {
|
|||
if (state.value === 'dead' || stage.value === 'egg') return;
|
||||
|
||||
// Decrease stats naturally
|
||||
// 目標:飢餓值約 30-60 分鐘下降 10%,快樂值約 20-40 分鐘下降 10%
|
||||
// TICK_RATE = 3000ms (3秒), 600 ticks = 30分鐘
|
||||
// 飢餓值每 tick -0.05 → 600 ticks = -30 (30分鐘下降30%)
|
||||
// 快樂值每 tick -0.08 → 600 ticks = -48 (30分鐘下降48%)
|
||||
// Destiny Effect: Gluttony (暴食) - Hunger decreases faster (+30%)
|
||||
let hungerDecay = 0.05;
|
||||
if (stats.value.destiny?.id === 'gluttony') {
|
||||
hungerDecay *= 1.3;
|
||||
}
|
||||
|
||||
// Destiny Effect: Playful (愛玩) - Happiness decreases faster
|
||||
let happinessDecay = 0.08;
|
||||
if (stats.value.destiny?.id === 'playful') {
|
||||
happinessDecay *= 1.2; // Faster decay
|
||||
}
|
||||
|
||||
// Destiny Effect: DEX (敏捷) - Hunger decreases slower
|
||||
// DEX 10 = -10% decay, DEX 50 = -50% decay
|
||||
if (stats.value.dex > 0) {
|
||||
const reduction = Math.min(0.5, stats.value.dex * 0.01); // Max 50% reduction
|
||||
hungerDecay *= (1 - reduction);
|
||||
}
|
||||
|
||||
if (state.value !== 'sleep') {
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - 0.05);
|
||||
stats.value.happiness = Math.max(0, stats.value.happiness - 0.08);
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - hungerDecay);
|
||||
stats.value.happiness = Math.max(0, stats.value.happiness - happinessDecay);
|
||||
} else {
|
||||
// Slower decay when sleeping (約 1/3 速度)
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - 0.015);
|
||||
stats.value.happiness = Math.max(0, stats.value.happiness - 0.025);
|
||||
stats.value.hunger = Math.max(0, stats.value.hunger - (hungerDecay * 0.3));
|
||||
stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
|
||||
}
|
||||
|
||||
// Random poop generation (更低的機率:約 0.5% per tick)
|
||||
|
|
@ -137,8 +188,14 @@ export function usePetSystem() {
|
|||
}
|
||||
|
||||
// Sickness Check (更低的生病機率)
|
||||
// Destiny Effect: Purification (淨化) - Sickness chance -20%
|
||||
let sickChance = 0.1;
|
||||
if (stats.value.destiny?.id === 'purification') {
|
||||
sickChance *= 0.8;
|
||||
}
|
||||
|
||||
if (stats.value.health < 30 && state.value !== 'sick') {
|
||||
if (Math.random() < 0.1) { // 從 0.3 降到 0.1
|
||||
if (Math.random() < sickChance) {
|
||||
state.value = 'sick';
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +281,10 @@ export function usePetSystem() {
|
|||
stats.value.happiness = 50;
|
||||
stats.value.health = 100;
|
||||
stats.value.poopCount = 0;
|
||||
|
||||
// v2: Assign Destiny
|
||||
assignDestiny();
|
||||
|
||||
isCleaning.value = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -238,11 +299,56 @@ export function usePetSystem() {
|
|||
health: 100,
|
||||
weight: 500,
|
||||
age: 1,
|
||||
poopCount: 0
|
||||
poopCount: 0,
|
||||
// v2 Reset
|
||||
str: 0,
|
||||
int: 0,
|
||||
dex: 0,
|
||||
generation: 1,
|
||||
deityFavor: 0,
|
||||
destiny: null
|
||||
};
|
||||
tickCount = 0;
|
||||
}
|
||||
|
||||
function resurrect() {
|
||||
if (state.value !== 'dead') return;
|
||||
|
||||
state.value = 'idle';
|
||||
stats.value.health = 50; // Revive with half health
|
||||
stats.value.happiness = 50;
|
||||
stats.value.hunger = 50;
|
||||
|
||||
// Penalty or Ghost Buff?
|
||||
// For now just a console log, maybe visual effect later
|
||||
console.log('Pet Resurrected!');
|
||||
}
|
||||
|
||||
function reincarnate() {
|
||||
// Inherit logic
|
||||
const prevDestiny = stats.value.destiny;
|
||||
const prevFavor = stats.value.deityFavor;
|
||||
const nextGen = (stats.value.generation || 1) + 1;
|
||||
|
||||
// Reset everything
|
||||
reset();
|
||||
|
||||
// Apply Inheritance
|
||||
stats.value.generation = nextGen;
|
||||
|
||||
// 20% Favor inheritance
|
||||
stats.value.deityFavor = Math.floor(prevFavor * 0.2);
|
||||
|
||||
// Destiny Inheritance (Optional: Maybe keep if it was a rare one?)
|
||||
// For now, let's say if you had a Rare (2) destiny, you keep it.
|
||||
// Otherwise, you get a new one on hatch.
|
||||
if (prevDestiny && prevDestiny.rarity === 2) {
|
||||
stats.value.destiny = prevDestiny;
|
||||
}
|
||||
|
||||
console.log('Pet Reincarnated to Gen', nextGen);
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
onMounted(() => {
|
||||
gameLoopId = setInterval(tick, TICK_RATE);
|
||||
|
|
@ -264,6 +370,9 @@ export function usePetSystem() {
|
|||
hatchEgg,
|
||||
reset,
|
||||
achievements,
|
||||
unlockAllAchievements
|
||||
unlockAllAchievements,
|
||||
assignDestiny, // Export for debug if needed
|
||||
resurrect,
|
||||
reincarnate
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue