good version

This commit is contained in:
王性驊 2025-11-22 21:25:35 +08:00
parent 1c068b1672
commit 53bdcb8a9d
6 changed files with 614 additions and 19 deletions

View File

@ -5,6 +5,7 @@ import DeviceScreen from './components/DeviceScreen.vue';
import PetGame from './components/PetGame.vue'; import PetGame from './components/PetGame.vue';
import Menu from './components/Menu.vue'; import Menu from './components/Menu.vue';
import { usePetSystem } from './composables/usePetSystem'; import { usePetSystem } from './composables/usePetSystem';
import { useEventSystem } from './composables/useEventSystem';
const currentScreen = ref('game'); const currentScreen = ref('game');
const petGameRef = ref(null); const petGameRef = ref(null);
@ -12,6 +13,7 @@ const showStats = ref(false); // Stats visibility
const debugAction = ref(null); // For passing debug commands to PetGame const debugAction = ref(null); // For passing debug commands to PetGame
// Initialize Pet System // Initialize Pet System
const petSystem = usePetSystem();
const { const {
stage, stage,
state, state,
@ -24,8 +26,19 @@ const {
hatchEgg, hatchEgg,
reset, reset,
achievements, achievements,
unlockAllAchievements unlockAllAchievements,
} = usePetSystem(); 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 // Handle Action Menu Events
function handleAction(action) { function handleAction(action) {
@ -88,6 +101,12 @@ function handleAction(action) {
console.log('求籤功能'); console.log('求籤功能');
// TODO: // TODO:
break; break;
case 'resurrect':
resurrect();
break;
case 'reincarnate':
reincarnate();
break;
default: default:
console.log('Action not implemented:', action); console.log('Action not implemented:', action);
} }
@ -117,6 +136,7 @@ function triggerDebugAction(action, payload = null) {
:showStats="showStats" :showStats="showStats"
:debugAction="debugAction" :debugAction="debugAction"
:achievements="achievements" :achievements="achievements"
:currentEvent="currentEvent"
@update:state="state = $event" @update:state="state = $event"
@action="handleAction" @action="handleAction"
/> />

View File

@ -35,7 +35,7 @@
</template> </template>
<!-- 求籤模式 --> <!-- 求籤模式 -->
<template v-else> <template v-else-if="mode === 'fortune'">
<!-- 失敗 (非聖筊) --> <!-- 失敗 (非聖筊) -->
<button v-if="resultType !== 'saint'" class="pixel-btn close-btn" @click="handleRetryFortune">重新求籤</button> <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> <button v-else class="pixel-btn retry-btn" @click="$emit('finish-fortune')">查看籤詩</button>
</template> </template>
</template> </template>
<!-- 招魂模式 -->
<template v-else-if="mode === 'resurrect'">
<button class="pixel-btn retry-btn" @click="$emit('resurrect-confirm')">確認</button>
</template>
</div> </div>
</div> </div>
</div> </div>

View File

@ -100,8 +100,21 @@
<!-- 生病骷髏頭 --> <!-- 生病骷髏頭 -->
<div class="sick-icon" :style="iconStyle" v-show="state === 'sick'">💀</div> <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 --> <!-- 便便 (Poop) - Scattered on sides -->
<div <div
@ -151,6 +164,17 @@
> >
<div :class="eventAnimation.iconClass"></div> <div :class="eventAnimation.iconClass"></div>
</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> </div>
<!-- Prayer Menu (覆蓋整個遊戲區域) --> <!-- Prayer Menu (覆蓋整個遊戲區域) -->
@ -169,6 +193,7 @@
@result="handleJiaobeiResult" @result="handleJiaobeiResult"
@retry-fortune="handleRetryFortune" @retry-fortune="handleRetryFortune"
@finish-fortune="handleFinishFortune" @finish-fortune="handleFinishFortune"
@resurrect-confirm="handleResurrectConfirm"
/> />
<!-- Fortune Stick Animation --> <!-- Fortune Stick Animation -->
@ -195,6 +220,12 @@
:hunger="stats?.hunger || 100" :hunger="stats?.hunger || 100"
:happiness="stats?.happiness || 100" :happiness="stats?.happiness || 100"
:health="stats?.health || 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" :achievements="achievements"
@close="showPetInfo = false" @close="showPetInfo = false"
/> />
@ -301,6 +332,10 @@ const props = defineProps({
achievements: { achievements: {
type: Array, type: Array,
default: () => [] default: () => []
},
currentEvent: {
type: Object,
default: null
} }
}); });
@ -315,6 +350,8 @@ const showFortuneResult = ref(false);
const currentLotData = ref(null); const currentLotData = ref(null);
const currentLotNumber = ref(null); const currentLotNumber = ref(null);
const consecutiveSaintCount = ref(0); const consecutiveSaintCount = ref(0);
const lastJiaobeiResult = ref(null); //
const showPetInfo = ref(false); const showPetInfo = ref(false);
const showInventory = ref(false); const showInventory = ref(false);
const showPlayMenu = ref(false); const showPlayMenu = ref(false);
@ -416,6 +453,7 @@ function handleTrainingAttack() {
function handleGameComplete(won) { function handleGameComplete(won) {
console.log('Game completed, won:', won); console.log('Game completed, won:', won);
const gameType = currentGame.value; // Capture before clearing
currentGame.value = ''; currentGame.value = '';
if (won) { if (won) {
@ -434,6 +472,20 @@ function handleGameComplete(won) {
// //
stats.value.happiness = Math.min(100, stats.value.happiness + 10); stats.value.happiness = Math.min(100, stats.value.happiness + 10);
stats.value.hunger = Math.max(0, stats.value.hunger - 5); // 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.happiness = Math.min(100, stats.value.happiness + 15);
stats.value.hunger = Math.max(0, stats.value.hunger - 10); 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,17 +539,65 @@ function handleFortuneStickClose() {
consecutiveSaintCount.value = 0; consecutiveSaintCount.value = 0;
} }
function handleResurrectAttempt() {
//
fortuneMode.value = 'resurrect';
showJiaobeiAnimation.value = true;
consecutiveSaintCount.value = 0;
}
function handleReincarnate() {
emit('action', 'reincarnate');
}
function handleJiaobeiResult(type) { function handleJiaobeiResult(type) {
if (fortuneMode.value === 'fortune') { if (fortuneMode.value === 'fortune') {
if (type === 'saint') { if (type === 'saint') {
consecutiveSaintCount.value++; 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; 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() { function handleRetryFortune() {
// //
showJiaobeiAnimation.value = false; showJiaobeiAnimation.value = false;
@ -505,6 +612,39 @@ function handleFinishFortune() {
currentLotData.value = lot; currentLotData.value = lot;
showJiaobeiAnimation.value = false; showJiaobeiAnimation.value = false;
showFortuneResult.value = true; 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 { } else {
console.error('Lot not found:', currentLotNumber.value); console.error('Lot not found:', currentLotNumber.value);
handleJiaobeiClose(); handleJiaobeiClose();
@ -1690,6 +1830,69 @@ defineExpose({
50% { transform: translate(-50%, -50%) translateY(-4px); } 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; 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 { .pixel-toy {
box-shadow: box-shadow:
-1px -2px #32cd32, 0px -2px #32cd32, 1px -2px #32cd32, -1px -2px #32cd32, 0px -2px #32cd32, 1px -2px #32cd32,

View File

@ -106,6 +106,43 @@
<div class="info-divider"></div> <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"> <div class="info-item">
<span class="label">HP</span> <span class="label">HP</span>
<span class="value">{{ baseStats.hp }}</span> <span class="value">{{ baseStats.hp }}</span>
@ -170,6 +207,13 @@ const props = defineProps({
type: Number, type: Number,
default: 100 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: { achievements: {
type: Array, type: Array,
default: () => [] default: () => []
@ -532,6 +576,31 @@ const weight = computed(() => {
font-weight: 600; 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 */
.achievements-view { .achievements-view {
padding: 12px; padding: 12px;

View File

@ -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
};
}

View File

@ -5,6 +5,17 @@ export function usePetSystem() {
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
// --- 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 --- // --- Stats ---
const stats = ref({ const stats = ref({
hunger: 100, // 0-100 (0 = Starving) hunger: 100, // 0-100 (0 = Starving)
@ -12,7 +23,14 @@ export function usePetSystem() {
health: 100, // 0-100 (0 = Sick risk) health: 100, // 0-100 (0 = Sick risk)
weight: 500, // grams weight: 500, // grams
age: 1, // days (start at day 1) 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([ const achievements = ref([
@ -32,6 +50,24 @@ export function usePetSystem() {
// --- Actions --- // --- 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() { 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;
@ -47,6 +83,7 @@ export function usePetSystem() {
stats.value.weight += 50; stats.value.weight += 50;
// Chance to poop after eating (降低機率) // 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 if (Math.random() < 0.15) { // 從 0.3 降到 0.15
setTimeout(() => { setTimeout(() => {
if (stats.value.poopCount < 4) { if (stats.value.poopCount < 4) {
@ -98,18 +135,32 @@ export function usePetSystem() {
if (state.value === 'dead' || stage.value === 'egg') return; if (state.value === 'dead' || stage.value === 'egg') return;
// Decrease stats naturally // Decrease stats naturally
// 目標:飢餓值約 30-60 分鐘下降 10%,快樂值約 20-40 分鐘下降 10% // Destiny Effect: Gluttony (暴食) - Hunger decreases faster (+30%)
// TICK_RATE = 3000ms (3秒), 600 ticks = 30分鐘 let hungerDecay = 0.05;
// 飢餓值每 tick -0.05 → 600 ticks = -30 (30分鐘下降30%) if (stats.value.destiny?.id === 'gluttony') {
// 快樂值每 tick -0.08 → 600 ticks = -48 (30分鐘下降48%) 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') { if (state.value !== 'sleep') {
stats.value.hunger = Math.max(0, stats.value.hunger - 0.05); stats.value.hunger = Math.max(0, stats.value.hunger - hungerDecay);
stats.value.happiness = Math.max(0, stats.value.happiness - 0.08); stats.value.happiness = Math.max(0, stats.value.happiness - happinessDecay);
} else { } else {
// Slower decay when sleeping (約 1/3 速度) // Slower decay when sleeping (約 1/3 速度)
stats.value.hunger = Math.max(0, stats.value.hunger - 0.015); stats.value.hunger = Math.max(0, stats.value.hunger - (hungerDecay * 0.3));
stats.value.happiness = Math.max(0, stats.value.happiness - 0.025); stats.value.happiness = Math.max(0, stats.value.happiness - (happinessDecay * 0.3));
} }
// Random poop generation (更低的機率:約 0.5% per tick) // Random poop generation (更低的機率:約 0.5% per tick)
@ -137,8 +188,14 @@ export function usePetSystem() {
} }
// Sickness Check (更低的生病機率) // 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 (stats.value.health < 30 && state.value !== 'sick') {
if (Math.random() < 0.1) { // 從 0.3 降到 0.1 if (Math.random() < sickChance) {
state.value = 'sick'; state.value = 'sick';
} }
} }
@ -224,6 +281,10 @@ export function usePetSystem() {
stats.value.happiness = 50; stats.value.happiness = 50;
stats.value.health = 100; stats.value.health = 100;
stats.value.poopCount = 0; stats.value.poopCount = 0;
// v2: Assign Destiny
assignDestiny();
isCleaning.value = false; isCleaning.value = false;
} }
} }
@ -238,11 +299,56 @@ export function usePetSystem() {
health: 100, health: 100,
weight: 500, weight: 500,
age: 1, age: 1,
poopCount: 0 poopCount: 0,
// v2 Reset
str: 0,
int: 0,
dex: 0,
generation: 1,
deityFavor: 0,
destiny: null
}; };
tickCount = 0; 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 --- // --- Lifecycle ---
onMounted(() => { onMounted(() => {
gameLoopId = setInterval(tick, TICK_RATE); gameLoopId = setInterval(tick, TICK_RATE);
@ -264,6 +370,9 @@ export function usePetSystem() {
hatchEgg, hatchEgg,
reset, reset,
achievements, achievements,
unlockAllAchievements unlockAllAchievements,
assignDestiny, // Export for debug if needed
resurrect,
reincarnate
}; };
} }