good version

This commit is contained in:
王性驊 2025-11-24 02:03:56 +08:00
commit ebe90953fe
23 changed files with 21810 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

139
README.md Normal file
View File

@ -0,0 +1,139 @@
# 虛擬寵物系統 - Nuxt 版本
基於 PRD v2.1 的資料驅動虛擬寵物系統支援事件觸發、Buff 管理、神明系統等核心功能。
## 🚀 快速開始
### 安裝依賴
```bash
npm install
```
### 啟動開發伺服器
```bash
npm run dev
```
### 使用 Console 互動版
1. 訪問:`http://localhost:3000/console-demo.html`
2. 打開瀏覽器開發者工具F12
3. 在 Console 中輸入命令,例如:
- `help()` - 查看所有命令
- `showStatus()` - 顯示寵物狀態
- `start()` - 啟動遊戲循環
- `feed(20)` - 餵食
- `play(15)` - 玩耍
- `pray()` - 祈福
詳細使用說明請參考 [docs/USAGE.md](./docs/USAGE.md)
## 📁 專案結構
- `data/` - 資料配置(寵物種族、神明、事件、道具、籤詩)
- `core/` - 核心系統API 服務、寵物系統、事件系統、神明系統)
- `console-demo.js` - Console 互動介面
- `public/console-demo.html` - 瀏覽器版本
- `docs/` - 文檔API 結構、使用說明)
## 🔌 API 整合
系統支援 Mock 和真實 API 切換:
- **Mock 模式**(預設):資料儲存在 localStorage
- **真實 API**:修改 `console-demo.js` 中的 `useMock: false`
API 端點結構詳見 [docs/API_STRUCTURE.md](./docs/API_STRUCTURE.md)
## 📚 文檔
- [使用說明](./docs/USAGE.md) - 完整的使用指南和命令列表
- [API 結構](./docs/API_STRUCTURE.md) - API 端點設計文檔
## 🎮 核心功能
- ✅ 寵物狀態管理(飢餓、快樂、健康等)
- ✅ Tick 循環系統(每 3 秒)
- ✅ 事件系統資料驅動10% 機率觸發)
- ✅ Buff 系統flat + percent 加成)
- ✅ 神明系統5 位神明,祈福、抽籤)
- ✅ API 服務層(支援 mock/real 切換)
- ✅ Console 互動介面
## 🛠️ 開發
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

1027
app/app.vue Normal file

File diff suppressed because it is too large Load Diff

371
console-demo.js Normal file
View File

@ -0,0 +1,371 @@
// Console 互動版本 - 可點擊操作所有功能
// 使用方式:在瀏覽器 console 或 Node.js 環境執行
import { PetSystem } from './core/pet-system.js'
import { EventSystem } from './core/event-system.js'
import { TempleSystem } from './core/temple-system.js'
import { ApiService } from './core/api-service.js'
// 創建 API 服務(可切換 mock/real
const apiService = new ApiService({
useMock: true, // 設為 false 可切換到真實 API
baseUrl: 'http://localhost:3000/api',
mockDelay: 100
})
// 全局系統實例
let petSystem, eventSystem, templeSystem
let isRunning = false
// 初始化系統
async function init() {
console.log('=== 虛擬寵物系統初始化 ===\n')
// 創建系統實例
petSystem = new PetSystem(apiService)
eventSystem = new EventSystem(petSystem, apiService)
templeSystem = new TempleSystem(petSystem, apiService)
// 初始化
await petSystem.initialize()
await eventSystem.initialize()
await templeSystem.initialize()
console.log('✅ 系統初始化完成!')
console.log('📝 輸入 help() 查看所有可用命令\n')
// 顯示初始狀態
showStatus()
return { petSystem, eventSystem, templeSystem }
}
// 顯示狀態
function showStatus() {
const state = petSystem.getState()
const buffs = eventSystem.getBuffManager().getActiveBuffs()
const currentDeity = templeSystem.getCurrentDeity()
const favorStars = templeSystem.getFavorStars(state.currentDeityId)
console.log('\n' + '='.repeat(50))
console.log('🐾 寵物狀態')
console.log('='.repeat(50))
console.log(`種類: ${state.speciesId}`)
console.log(`階段: ${state.stage}`)
console.log(`年齡: ${Math.floor(state.ageSeconds)}`)
console.log(`\n📊 基礎數值:`)
console.log(` 飢餓: ${state.hunger.toFixed(1)}/100`)
console.log(` 快樂: ${state.happiness.toFixed(1)}/100`)
console.log(` 健康: ${state.health.toFixed(1)}/100`)
console.log(` 體重: ${state.weight.toFixed(1)}`)
console.log(`\n💪 屬性:`)
console.log(` 力量: ${state.str.toFixed(1)}`)
console.log(` 智力: ${state.int.toFixed(1)}`)
console.log(` 敏捷: ${state.dex.toFixed(1)}`)
console.log(` 運勢: ${state.luck.toFixed(1)}`)
console.log(`\n🎭 狀態:`)
console.log(` 睡覺: ${state.isSleeping ? '是' : '否'}`)
console.log(` 生病: ${state.isSick ? '是' : '否'}`)
console.log(` 死亡: ${state.isDead ? '是' : '否'}`)
console.log(` 便便: ${state.poopCount}/4`)
console.log(`\n🙏 神明:`)
console.log(` 當前: ${currentDeity.name}`)
console.log(` 好感: ${favorStars} (${state.deityFavors[state.currentDeityId]}/100)`)
console.log(` 今日祈福: ${state.dailyPrayerCount}/3`)
console.log(`\n✨ 當前 Buff:`)
if (buffs.length === 0) {
console.log(' (無)')
} else {
buffs.forEach(b => {
const duration = b.durationTicks === Infinity ? '永久' : `${b.currentTicks} ticks`
console.log(` - ${b.name} (${duration})`)
})
}
console.log('='.repeat(50) + '\n')
}
// 啟動遊戲循環
function start() {
if (isRunning) {
console.log('⚠️ 遊戲循環已在運行中')
return
}
isRunning = true
petSystem.startTickLoop((state) => {
console.log(`\n⏰ Tick: ${new Date().toLocaleTimeString()}`)
showStatus()
})
eventSystem.startEventCheck()
console.log('✅ 遊戲循環已啟動(每 3 秒 tick每 10 秒檢查事件)')
console.log('💡 輸入 stop() 停止循環\n')
}
// 停止遊戲循環
function stop() {
if (!isRunning) {
console.log('⚠️ 遊戲循環未運行')
return
}
petSystem.stopTickLoop()
eventSystem.stopEventCheck()
isRunning = false
console.log('⏹️ 遊戲循環已停止')
}
// ========== 互動命令 ==========
// 餵食
async function feed(amount = 20) {
const result = await petSystem.feed(amount)
if (result.success) {
console.log(`✅ 餵食成功!飢餓 +${amount},體重 +${(amount * 0.5).toFixed(1)}`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 玩耍
async function play(amount = 15) {
const result = await petSystem.play(amount)
if (result.success) {
console.log(`✅ 玩耍成功!快樂 +${amount},敏捷 +0.5`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 清理便便
async function clean() {
const result = await petSystem.cleanPoop()
if (result.success) {
console.log('✅ 清理成功!快樂 +10')
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 治療
async function heal(amount = 20) {
const result = await petSystem.heal(amount)
if (result.success) {
console.log(`✅ 治療成功!健康 +${amount}${result.cured ? ',疾病已治癒' : ''}`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 睡覺/起床
async function sleep() {
const result = await petSystem.toggleSleep()
if (result.success) {
console.log(`${result.isSleeping ? '寵物已入睡' : '寵物已醒來'}`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 觸發事件(測試用)
async function triggerEvent(eventId) {
const result = await eventSystem.triggerEvent(eventId)
if (result) {
console.log(`✅ 事件 ${eventId} 觸發成功`)
} else {
console.log(`❌ 事件 ${eventId} 觸發失敗或條件不滿足`)
}
showStatus()
}
// 查看事件列表
async function listEvents() {
const events = await apiService.getEvents()
console.log('\n📋 可用事件列表:')
console.log('='.repeat(50))
events.forEach(e => {
console.log(`\n${e.id} (${e.type})`)
console.log(` 權重: ${e.weight}`)
console.log(` 效果數: ${e.effects.length}`)
})
console.log('='.repeat(50) + '\n')
}
// 查看事件歷史
function history() {
const history = eventSystem.getHistory()
console.log('\n📜 事件歷史:')
console.log('='.repeat(50))
if (history.length === 0) {
console.log(' (無)')
} else {
history.forEach((h, i) => {
const time = new Date(h.timestamp).toLocaleTimeString()
console.log(`${i + 1}. [${time}] ${h.eventId} (${h.eventType})`)
})
}
console.log('='.repeat(50) + '\n')
}
// 祈福
async function pray() {
const result = await templeSystem.pray()
if (result.success) {
console.log(`✅ 祈福成功!`)
console.log(` 好感度 +${result.favorIncrease}${result.newFavor}`)
console.log(` ${result.dialogue}`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 抽籤
async function drawFortune() {
const result = await templeSystem.drawFortune()
if (result.success) {
console.log('\n🎴 抽籤結果:')
console.log('='.repeat(50))
console.log(`等級: ${result.lot.grade}`)
console.log(`籤詩: ${result.lot.poem1}`)
console.log(` ${result.lot.poem2}`)
console.log(`解釋: ${result.lot.meaning}`)
if (result.buff) {
console.log(`\n✨ 獲得 Buff: ${result.buff.name}`)
}
console.log('='.repeat(50) + '\n')
} else {
console.log(`${result.message}`)
}
}
// 切換神明
async function switchDeity(deityId) {
const result = await templeSystem.switchDeity(deityId)
if (result.success) {
console.log(`✅ 已切換到 ${result.deity.name}`)
} else {
console.log(`${result.message}`)
}
showStatus()
}
// 查看神明列表
function listDeities() {
const deities = templeSystem.getDeities()
console.log('\n🙏 神明列表:')
console.log('='.repeat(50))
deities.forEach(d => {
const state = petSystem.getState()
const favor = state.deityFavors[d.id] || 0
const stars = templeSystem.getFavorStars(d.id)
console.log(`\n${d.id}: ${d.name}`)
console.log(` 個性: ${d.personality}`)
console.log(` 好感: ${stars} (${favor}/100)`)
console.log(` 加成: ${d.buffDescriptions.join(', ')}`)
})
console.log('='.repeat(50) + '\n')
}
// 應用 Buff每 tick 自動執行,也可手動)
async function applyBuffs() {
await eventSystem.applyBuffs()
eventSystem.getBuffManager().tick()
console.log('✅ Buff 已應用並更新')
showStatus()
}
// 幫助
function help() {
console.log('\n' + '='.repeat(50))
console.log('📖 可用命令列表')
console.log('='.repeat(50))
console.log('\n🎮 遊戲控制:')
console.log(' start() - 啟動遊戲循環')
console.log(' stop() - 停止遊戲循環')
console.log(' showStatus() - 顯示當前狀態')
console.log('\n🐾 寵物互動:')
console.log(' feed(amount) - 餵食(預設 +20')
console.log(' play(amount) - 玩耍(預設 +15')
console.log(' clean() - 清理便便')
console.log(' heal(amount) - 治療(預設 +20')
console.log(' sleep() - 睡覺/起床')
console.log('\n🎲 事件系統:')
console.log(' triggerEvent(id) - 手動觸發事件')
console.log(' listEvents() - 查看所有事件')
console.log(' history() - 查看事件歷史')
console.log(' applyBuffs() - 手動應用 Buff')
console.log('\n🙏 神明系統:')
console.log(' pray() - 祈福(每日 3 次)')
console.log(' drawFortune() - 抽籤')
console.log(' switchDeity(id) - 切換神明')
console.log(' listDeities() - 查看神明列表')
console.log('\n💡 提示:')
console.log(' - 所有數值操作都會同步到 APImock 模式使用 localStorage')
console.log(' - 事件每 10 秒自動檢查10% 機率觸發)')
console.log(' - 遊戲循環每 3 秒執行一次 tick')
console.log(' - 輸入 help() 再次查看此列表')
console.log('='.repeat(50) + '\n')
}
// 匯出到全局(瀏覽器環境)
if (typeof window !== 'undefined') {
window.petSystem = petSystem
window.eventSystem = eventSystem
window.templeSystem = templeSystem
window.showStatus = showStatus
window.start = start
window.stop = stop
window.feed = feed
window.play = play
window.clean = clean
window.heal = heal
window.sleep = sleep
window.triggerEvent = triggerEvent
window.listEvents = listEvents
window.history = history
window.pray = pray
window.drawFortune = drawFortune
window.switchDeity = switchDeity
window.listDeities = listDeities
window.applyBuffs = applyBuffs
window.help = help
window.init = init
}
// Node.js 環境自動初始化
if (typeof window === 'undefined') {
init().then(() => {
console.log('\n💡 提示:在瀏覽器環境中,這些函數會自動掛載到 window 物件')
console.log(' 在 Node.js 環境中,請使用 await 呼叫這些函數\n')
})
}
export {
init,
showStatus,
start,
stop,
feed,
play,
clean,
heal,
sleep,
triggerEvent,
listEvents,
history,
pray,
drawFortune,
switchDeity,
listDeities,
applyBuffs,
help
}

262
core/api-service.js Normal file
View File

@ -0,0 +1,262 @@
// API 服務層 - 支援 mock 和真實 API 切換
export class ApiService {
constructor(config = {}) {
this.baseUrl = config.baseUrl || 'http://localhost:3000/api'
this.useMock = config.useMock !== false // 預設使用 mock
this.mockDelay = config.mockDelay || 100 // mock 延遲模擬網路請求
}
// 通用請求方法
async request(endpoint, options = {}) {
if (this.useMock) {
return this.mockRequest(endpoint, options)
}
return this.realRequest(endpoint, options)
}
// Mock 請求(使用本地資料)
async mockRequest(endpoint, options) {
await this.delay(this.mockDelay)
const [resource, action] = endpoint.split('/').filter(Boolean)
switch (`${resource}/${action}`) {
case 'pet/state':
return this.getMockPetState()
case 'pet/update':
return this.updateMockPetState(options.body)
case 'events/list':
return this.getMockEvents()
case 'events/trigger':
return this.triggerMockEvent(options.body)
case 'buffs/apply':
return this.applyMockBuff(options.body)
case 'deities/list':
return this.getMockDeities()
case 'deities/pray':
return this.mockPrayToDeity(options.body)
case 'fortune/draw':
return this.mockDrawFortune()
case 'items/list':
return this.getMockItems()
case 'items/use':
return this.mockUseItem(options.body)
case 'pet/save':
return this.saveMockPetState(options.body)
case 'pet/delete':
return this.deleteMockPetState()
default:
throw new Error(`Unknown endpoint: ${endpoint}`)
}
}
// 真實 API 請求
async realRequest(endpoint, options = {}) {
const url = `${this.baseUrl}/${endpoint}`
const config = {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers
}
}
if (options.body) {
config.body = JSON.stringify(options.body)
}
const response = await fetch(url, config)
if (!response.ok) {
throw new Error(`API Error: ${response.status} ${response.statusText}`)
}
return response.json()
}
// 延遲模擬
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
// ========== API 端點方法 ==========
// 寵物狀態相關
async getPetState() {
return this.request('pet/state')
}
async updatePetState(updates) {
return this.request('pet/update', {
method: 'POST',
body: updates
})
}
async savePetState(state) {
return this.request('pet/save', {
method: 'POST',
body: state
})
}
async deletePetState() {
return this.request('pet/delete', {
method: 'DELETE'
})
}
// 事件相關
async getEvents() {
return this.request('events/list')
}
async triggerEvent(eventId, petState) {
return this.request('events/trigger', {
method: 'POST',
body: { eventId, petState }
})
}
// Buff 相關
async applyBuff(buffData) {
return this.request('buffs/apply', {
method: 'POST',
body: buffData
})
}
// 神明相關
async getDeities() {
return this.request('deities/list')
}
async prayToDeity(deityId, petState) {
return this.request('deities/pray', {
method: 'POST',
body: { deityId, petState }
})
}
// 籤詩相關
async drawFortune() {
return this.request('fortune/draw', {
method: 'POST'
})
}
// 道具相關
async getItems() {
return this.request('items/list')
}
async useItem(itemId, count = 1) {
return this.request('items/use', {
method: 'POST',
body: { itemId, count }
})
}
// ========== Mock 資料方法 ==========
getMockPetState() {
// 從 localStorage 或預設值讀取
const stored = localStorage.getItem('petState')
if (stored) {
return JSON.parse(stored)
}
return null
}
updateMockPetState(updates) {
const current = this.getMockPetState()
const updated = { ...current, ...updates }
localStorage.setItem('petState', JSON.stringify(updated))
return { success: true, data: updated }
}
saveMockPetState(state) {
localStorage.setItem('petState', JSON.stringify(state))
return { success: true, message: '狀態已儲存' }
}
getMockEvents() {
// 動態載入事件配置
return import('../data/events.js').then(m => m.EVENT_CONFIG)
}
triggerMockEvent({ eventId, petState }) {
// 模擬事件觸發邏輯
return { success: true, eventId, effects: [] }
}
applyMockBuff(buffData) {
return { success: true, buff: buffData }
}
async getMockDeities() {
const { DEITIES } = await import('../data/deities.js')
return DEITIES
}
mockPrayToDeity({ deityId, petState }) {
return {
success: true,
favorIncrease: 5,
message: '祈福成功'
}
}
mockDrawFortune() {
// 模擬抽籤邏輯
const grades = ['上上', '上', '中', '下', '下下']
const weights = [0.05, 0.15, 0.40, 0.30, 0.10]
const random = Math.random()
let sum = 0
let selectedGrade = '中'
for (let i = 0; i < weights.length; i++) {
sum += weights[i]
if (random <= sum) {
selectedGrade = grades[i]
break
}
}
return {
success: true,
lot: {
id: Math.floor(Math.random() * 100) + 1,
grade: selectedGrade,
poem1: '示例籤詩',
meaning: '示例解釋'
}
}
}
async getMockItems() {
const { ITEMS } = await import('../data/items.js')
return Object.values(ITEMS)
}
mockUseItem({ itemId, count }) {
return {
success: true,
itemId,
count,
effects: {}
}
}
deleteMockPetState() {
localStorage.removeItem('petState')
return { success: true, message: '寵物已刪除' }
}
}
// 預設實例
export const apiService = new ApiService({
useMock: true, // 開發時使用 mock
mockDelay: 100
})

323
core/event-system.js Normal file
View File

@ -0,0 +1,323 @@
// 事件系統核心 - 與 API 整合
import { apiService } from './api-service.js'
export class EventSystem {
constructor(petSystem, api = apiService) {
this.petSystem = petSystem
this.api = api
this.events = []
this.buffManager = new BuffManager()
this.eventHistory = []
this.eventCheckInterval = null
this.lastEventCheckTime = 0
}
// 初始化(從 API 載入事件配置)
async initialize() {
try {
this.events = await this.api.getEvents()
console.log(`[EventSystem] 載入 ${this.events.length} 個事件`)
} catch (error) {
console.error('[EventSystem] 載入事件失敗:', error)
// 降級到本地載入
const { EVENT_CONFIG } = await import('../data/events.js')
this.events = EVENT_CONFIG
}
}
// 啟動事件檢查循環(每 10 秒檢查一次)
startEventCheck() {
if (this.eventCheckInterval) this.stopEventCheck()
this.eventCheckInterval = setInterval(() => {
this.checkTriggers()
}, 10000) // 每 10 秒
}
stopEventCheck() {
if (this.eventCheckInterval) {
clearInterval(this.eventCheckInterval)
this.eventCheckInterval = null
}
}
// 隨機選擇事件(依權重)
selectRandomEvent() {
const totalWeight = this.events.reduce((sum, e) => sum + e.weight, 0)
let rand = Math.random() * totalWeight
for (const event of this.events) {
rand -= event.weight
if (rand <= 0) return event
}
return this.events[0] // fallback
}
// 檢查觸發條件10% 機率 + 條件檢查)
async checkTriggers() {
const petState = this.petSystem.getState()
if (petState.isDead) return
// 10% 機率觸發
if (Math.random() >= 0.1) return
const event = this.selectRandomEvent()
// 檢查條件
if (event.condition && !event.condition(petState)) {
return
}
// 觸發事件
await this.triggerEvent(event.id, petState)
}
// 觸發事件(同步到 API
async triggerEvent(eventId, petState = null) {
const event = this.events.find(e => e.id === eventId)
if (!event) {
console.warn(`[EventSystem] 找不到事件: ${eventId}`)
return null
}
const currentState = petState || this.petSystem.getState()
// 檢查條件
if (event.condition && !event.condition(currentState)) {
console.log(`[EventSystem] 事件 ${eventId} 條件不滿足`)
return null
}
console.log(`\n[事件觸發] ${event.id} (${event.type}): 機率檢查通過!`)
// 執行效果
const results = []
for (const effect of event.effects) {
const result = await this.executeEffect(effect, currentState)
results.push(result)
}
// 記錄歷史
this.eventHistory.push({
timestamp: Date.now(),
eventId: event.id,
eventType: event.type,
effects: results
})
// 同步到 API
try {
await this.api.triggerEvent(eventId, currentState)
} catch (error) {
console.warn('[EventSystem] API 同步失敗:', error)
}
return { event, results }
}
// 執行效果
async executeEffect(effect, petState) {
switch (effect.type) {
case 'modifyStats':
return await this.modifyStats(effect.payload, petState)
case 'addBuff':
return await this.addBuff(effect.payload)
case 'spawnPoop':
return await this.spawnPoop(effect.payload)
case 'templeFavor':
return await this.modifyTempleFavor(effect.payload)
case 'logMessage':
console.log(`[訊息] ${effect.payload}`)
return { type: 'logMessage', message: effect.payload }
default:
console.warn(`[EventSystem] 未知效果類型: ${effect.type}`)
return null
}
}
// 修改屬性
async modifyStats(payload, petState) {
const updates = {}
for (const [key, value] of Object.entries(payload)) {
if (key === 'isSleeping' || key === 'isSick' || key === 'isDead') {
updates[key] = value
} else {
const current = petState[key] || 0
const newValue = Math.max(0, Math.min(100, current + value))
updates[key] = newValue
console.log(`[效果] ${key} ${value > 0 ? '+' : ''}${value} → 新值: ${newValue}`)
}
}
await this.petSystem.updateState(updates)
return { type: 'modifyStats', updates }
}
// 添加 Buff
async addBuff(buffData) {
this.buffManager.addBuff(buffData)
// 同步到 API
try {
await this.api.applyBuff(buffData)
} catch (error) {
console.warn('[EventSystem] Buff API 同步失敗:', error)
}
return { type: 'addBuff', buff: buffData }
}
// 生成便便
async spawnPoop(payload) {
const count = payload?.count || 1
const currentPoop = this.petSystem.getState().poopCount
const newPoop = Math.min(4, currentPoop + count)
await this.petSystem.updateState({ poopCount: newPoop })
console.log(`[效果] 生成便便: ${count} 個,總數: ${newPoop}`)
return { type: 'spawnPoop', count, total: newPoop }
}
// 修改神明好感度
async modifyTempleFavor(payload) {
const { deityId, amount } = payload
const currentState = this.petSystem.getState()
const currentFavor = currentState.deityFavors[deityId] || 0
const newFavor = Math.max(0, Math.min(100, currentFavor + amount))
await this.petSystem.updateState({
deityFavors: {
...currentState.deityFavors,
[deityId]: newFavor
}
})
console.log(`[效果] ${deityId} 好感度 ${amount > 0 ? '+' : ''}${amount}${newFavor}`)
return { type: 'templeFavor', deityId, favor: newFavor }
}
// 應用 Buff 到狀態(每 tick 呼叫)
async applyBuffs() {
const petState = this.petSystem.getState()
const buffs = this.buffManager.getActiveBuffs()
// 計算最終屬性(考慮 Buff
const finalStats = this.calculateFinalStats(petState, buffs)
// 更新狀態
await this.petSystem.updateState(finalStats)
return finalStats
}
// 計算最終屬性Base + Flat + Percent
calculateFinalStats(baseState, buffs) {
const stats = { ...baseState }
// 收集所有 Buff 的 flat 和 percent 加成
const flatMods = {}
const percentMods = {}
buffs.forEach(buff => {
if (buff.flat) {
Object.entries(buff.flat).forEach(([key, value]) => {
flatMods[key] = (flatMods[key] || 0) + value
})
}
if (buff.percent) {
Object.entries(buff.percent).forEach(([key, value]) => {
percentMods[key] = (percentMods[key] || 0) + value
})
}
})
// 應用加成
Object.keys(stats).forEach(key => {
if (typeof stats[key] === 'number' && key !== 'ageSeconds' && key !== 'weight' && key !== 'generation') {
const base = stats[key]
const flat = flatMods[key] || 0
const percent = percentMods[key] || 0
stats[key] = Math.max(0, Math.min(100, (base + flat) * (1 + percent)))
}
})
return stats
}
// 獲取事件歷史
getHistory() {
return [...this.eventHistory]
}
// 獲取 Buff 管理器
getBuffManager() {
return this.buffManager
}
}
// Buff 管理器
class BuffManager {
constructor() {
this.buffs = []
}
addBuff(buff) {
// 檢查是否可堆疊
const existing = this.buffs.find(b => b.id === buff.id)
if (existing && !buff.stacks) {
// 不可堆疊,重置持續時間
existing.currentTicks = buff.durationTicks
console.log(`[Buff] 重置: ${buff.name} (持續 ${buff.durationTicks} ticks)`)
} else {
// 可堆疊或新 Buff
this.buffs.push({
...buff,
currentTicks: buff.durationTicks,
currentStacks: (existing?.currentStacks || 0) + 1
})
console.log(`[Buff] 新增: ${buff.name} (持續 ${buff.durationTicks} ticks)`)
}
}
tick() {
this.buffs = this.buffs.filter(b => {
if (b.durationTicks === Infinity) return true // 永久 Buff
b.currentTicks--
if (b.currentTicks <= 0) {
console.log(`[Buff] 過期: ${b.name}`)
return false
}
return true
})
}
getActiveBuffs() {
return this.buffs.filter(b => b.currentTicks > 0 || b.durationTicks === Infinity)
}
getFinalModifier(statKey) {
const activeBuffs = this.getActiveBuffs()
let flatTotal = 0
let percentTotal = 0
activeBuffs.forEach(b => {
if (b.flat?.[statKey]) flatTotal += b.flat[statKey]
if (b.percent?.[statKey]) percentTotal += b.percent[statKey]
})
return { flat: flatTotal, percent: percentTotal }
}
}

286
core/pet-system.js Normal file
View File

@ -0,0 +1,286 @@
// 寵物系統核心 - 與 API 整合
import { apiService } from './api-service.js'
import { PET_SPECIES } from '../data/pet-species.js'
export class PetSystem {
constructor(api = apiService) {
this.api = api
this.state = null
this.speciesConfig = null
this.tickInterval = null
this.eventCheckInterval = null
}
// 初始化寵物(從 API 載入或創建新寵物)
async initialize(speciesId = 'tinyTigerCat') {
try {
// 從 API 載入現有狀態
this.state = await this.api.getPetState()
if (!this.state) {
// 創建新寵物
this.state = this.createInitialState(speciesId)
await this.api.savePetState(this.state)
}
// 載入種族配置
this.speciesConfig = PET_SPECIES[this.state.speciesId] || PET_SPECIES[speciesId]
return this.state
} catch (error) {
console.error('[PetSystem] 初始化失敗:', error)
// 降級到本地狀態
this.state = this.createInitialState(speciesId)
this.speciesConfig = PET_SPECIES[speciesId]
return this.state
}
}
// 創建初始狀態
createInitialState(speciesId) {
const config = PET_SPECIES[speciesId]
return {
speciesId,
stage: 'baby',
hunger: 100,
happiness: 100,
health: 100,
weight: 500,
ageSeconds: 0,
poopCount: 0,
str: 0,
int: 0,
dex: 0,
luck: config.baseStats.luck || 10,
isSleeping: false,
isSick: false,
isDead: false,
currentDeityId: 'mazu',
deityFavors: {
mazu: 0,
earthgod: 0,
yuelao: 0,
wenchang: 0,
guanyin: 0
},
dailyPrayerCount: 0,
destiny: null,
buffs: [],
inventory: [],
generation: 1,
lastTickTime: Date.now()
}
}
// 更新狀態(同步到 API
async updateState(updates) {
this.state = { ...this.state, ...updates }
try {
await this.api.updatePetState(updates)
} catch (error) {
console.warn('[PetSystem] API 更新失敗,使用本地狀態:', error)
}
return this.state
}
// 獲取當前狀態
getState() {
return { ...this.state }
}
// 刪除寵物
async deletePet() {
try {
// 停止所有循環
this.stopTickLoop()
// 從 API 刪除
await this.api.deletePetState()
// 重置狀態
this.state = null
this.speciesConfig = null
return { success: true, message: '寵物已刪除' }
} catch (error) {
console.error('[PetSystem] 刪除寵物失敗:', error)
// 即使 API 失敗,也清除本地狀態
this.state = null
this.speciesConfig = null
return { success: true, message: '寵物已刪除(本地)' }
}
}
// Tick 循環(每 3 秒)
startTickLoop(callback) {
if (this.tickInterval) this.stopTickLoop()
this.tickInterval = setInterval(async () => {
await this.tick()
if (callback) callback(this.getState())
}, 3000)
}
stopTickLoop() {
if (this.tickInterval) {
clearInterval(this.tickInterval)
this.tickInterval = null
}
}
// 單次 Tick 執行
async tick() {
if (!this.state || this.state.isDead) return
const now = Date.now()
const deltaTime = (now - (this.state.lastTickTime || now)) / 1000
this.state.lastTickTime = now
// 年齡增長
this.state.ageSeconds += deltaTime
// 檢查階段轉換
this.checkStageTransition()
// 基礎數值衰減
const config = this.speciesConfig.baseStats
this.state.hunger = Math.max(0, this.state.hunger - config.hungerDecayPerTick * 100)
this.state.happiness = Math.max(0, this.state.happiness - config.happinessDecayPerTick * 100)
// 便便機率
if (Math.random() < config.poopChancePerTick) {
this.state.poopCount = Math.min(4, this.state.poopCount + 1)
}
// 生病機率
if (!this.state.isSick && Math.random() < config.sicknessChance) {
this.state.isSick = true
await this.updateState({ isSick: true })
}
// 健康檢查
if (this.state.health <= 0) {
this.state.isDead = true
await this.updateState({ isDead: true })
}
// 同步到 API
await this.updateState({
ageSeconds: this.state.ageSeconds,
hunger: this.state.hunger,
happiness: this.state.happiness,
poopCount: this.state.poopCount,
isSick: this.state.isSick,
isDead: this.state.isDead
})
}
// 檢查階段轉換
checkStageTransition() {
const config = this.speciesConfig
const currentStage = this.state.stage
const ageSeconds = this.state.ageSeconds
for (const stageConfig of config.lifecycle) {
if (stageConfig.stage === currentStage) {
// 找到下一個階段
const currentIndex = config.lifecycle.findIndex(s => s.stage === currentStage)
if (currentIndex < config.lifecycle.length - 1) {
const nextStage = config.lifecycle[currentIndex + 1]
if (ageSeconds >= nextStage.durationSeconds && nextStage.durationSeconds > 0) {
this.state.stage = nextStage.stage
this.updateState({ stage: nextStage.stage })
}
}
break
}
}
}
// 餵食
async feed(amount = 20) {
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
const newHunger = Math.min(100, this.state.hunger + amount)
const newWeight = this.state.weight + (amount * 0.5)
await this.updateState({
hunger: newHunger,
weight: newWeight
})
return { success: true, hunger: newHunger, weight: newWeight }
}
// 玩耍
async play(amount = 15) {
if (this.state.isDead || this.state.isSleeping) {
return { success: false, message: '寵物無法玩耍' }
}
const newHappiness = Math.min(100, this.state.happiness + amount)
const newDex = this.state.dex + 0.5
await this.updateState({
happiness: newHappiness,
dex: newDex
})
return { success: true, happiness: newHappiness, dex: newDex }
}
// 清理便便
async cleanPoop() {
if (this.state.poopCount === 0) {
return { success: false, message: '沒有便便需要清理' }
}
const newPoopCount = 0
const newHappiness = Math.min(100, this.state.happiness + 10)
await this.updateState({
poopCount: newPoopCount,
happiness: newHappiness
})
return { success: true, poopCount: newPoopCount, happiness: newHappiness }
}
// 治療
async heal(amount = 20) {
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
const newHealth = Math.min(100, this.state.health + amount)
const wasSick = this.state.isSick
await this.updateState({
health: newHealth,
isSick: false
})
return {
success: true,
health: newHealth,
isSick: false,
cured: wasSick && !this.state.isSick
}
}
// 睡覺/起床
async toggleSleep() {
if (this.state.isDead) return { success: false, message: '寵物已死亡' }
const newIsSleeping = !this.state.isSleeping
const healthChange = newIsSleeping ? 5 : 0
await this.updateState({
isSleeping: newIsSleeping,
health: Math.min(100, this.state.health + healthChange)
})
return { success: true, isSleeping: newIsSleeping }
}
}

125
core/temple-system.js Normal file
View File

@ -0,0 +1,125 @@
// 神明系統 - 與 API 整合
import { apiService } from './api-service.js'
export class TempleSystem {
constructor(petSystem, api = apiService) {
this.petSystem = petSystem
this.api = api
this.deities = []
}
// 初始化(從 API 載入神明資料)
async initialize() {
try {
this.deities = await this.api.getDeities()
console.log(`[TempleSystem] 載入 ${this.deities.length} 位神明`)
} catch (error) {
console.error('[TempleSystem] 載入神明失敗:', error)
// 降級到本地載入
const { DEITIES } = await import('../data/deities.js')
this.deities = DEITIES
}
}
// 獲取所有神明
getDeities() {
return [...this.deities]
}
// 獲取當前神明
getCurrentDeity() {
const state = this.petSystem.getState()
return this.deities.find(d => d.id === state.currentDeityId) || this.deities[0]
}
// 切換神明
async switchDeity(deityId) {
const deity = this.deities.find(d => d.id === deityId)
if (!deity) {
return { success: false, message: '找不到該神明' }
}
await this.petSystem.updateState({ currentDeityId: deityId })
return { success: true, deity }
}
// 祈福(每日上限 3 次)
async pray() {
const state = this.petSystem.getState()
if (state.dailyPrayerCount >= 3) {
return { success: false, message: '今日祈福次數已用完' }
}
try {
const result = await this.api.prayToDeity({
deityId: state.currentDeityId,
petState: state
})
// 更新好感度
const currentFavor = state.deityFavors[state.currentDeityId] || 0
const newFavor = Math.min(100, currentFavor + result.favorIncrease)
await this.petSystem.updateState({
deityFavors: {
...state.deityFavors,
[state.currentDeityId]: newFavor
},
dailyPrayerCount: state.dailyPrayerCount + 1
})
// 獲取神明對話
const deity = this.getCurrentDeity()
const dialogue = deity.dialogues[Math.floor(Math.random() * deity.dialogues.length)]
return {
success: true,
favorIncrease: result.favorIncrease,
newFavor,
dialogue,
message: result.message
}
} catch (error) {
console.error('[TempleSystem] 祈福失敗:', error)
return { success: false, message: '祈福失敗' }
}
}
// 獲取好感度星級(每 20 點一星)
getFavorStars(deityId) {
const state = this.petSystem.getState()
const favor = state.deityFavors[deityId] || 0
const stars = Math.floor(favor / 20)
return '★'.repeat(stars) + '☆'.repeat(5 - stars)
}
// 抽籤
async drawFortune() {
try {
const result = await this.api.drawFortune()
// 根據籤詩等級應用效果
const { FORTUNE_LOTS } = await import('../data/fortune-lots.js')
const lot = FORTUNE_LOTS.find(l => l.grade === result.lot.grade) || FORTUNE_LOTS[0]
if (lot.effects?.addBuff) {
// 應用 Buff需要透過 eventSystem
return {
success: true,
lot: result.lot,
buff: lot.effects.addBuff
}
}
return {
success: true,
lot: result.lot
}
} catch (error) {
console.error('[TempleSystem] 抽籤失敗:', error)
return { success: false, message: '抽籤失敗' }
}
}
}

86
data/deities.js Normal file
View File

@ -0,0 +1,86 @@
// 神明資料配置
export const DEITIES = [
{
id: 'mazu',
name: '媽祖',
personality: '溫柔守護',
buffs: {
gameSuccessRate: 0.1,
sicknessReduction: 0.15,
happinessRecovery: 0.25
},
buffDescriptions: ['小遊戲 +10%', '生病機率 -15%', '快樂恢復 +25%'],
dialogues: [
'好孩子,媽祖保佑你平安喔',
'海上無風浪,心中有媽祖',
'要好好照顧寵物啊',
'心誠則靈,媽祖會守護你的'
],
icon: 'deity-mazu'
},
{
id: 'earthgod',
name: '土地公',
personality: '親切和藹',
buffs: {
dropRate: 0.2,
resourceGain: 0.15
},
buffDescriptions: ['掉落率 +20%', '資源獲得 +15%'],
dialogues: [
'土地公保佑,財源廣進',
'好好照顧寵物,會有福報的',
'心善之人,必有善報'
],
icon: 'deity-earthgod'
},
{
id: 'yuelao',
name: '月老',
personality: '浪漫溫和',
buffs: {
happinessRecovery: 0.3,
breedingSuccess: 0.2
},
buffDescriptions: ['快樂恢復 +30%', '繁殖成功率 +20%'],
dialogues: [
'有緣千里來相會',
'好好對待寵物,感情會更深厚',
'愛心滿滿,幸福滿滿'
],
icon: 'deity-yuelao'
},
{
id: 'wenchang',
name: '文昌',
personality: '智慧嚴謹',
buffs: {
intGain: 0.25,
miniGameBonus: 0.15
},
buffDescriptions: ['智力成長 +25%', '小遊戲獎勵 +15%'],
dialogues: [
'勤學不輟,智慧增長',
'好好學習,寵物也會變聰明',
'知識就是力量'
],
icon: 'deity-wenchang'
},
{
id: 'guanyin',
name: '觀音',
personality: '慈悲寬容',
buffs: {
healthRecovery: 0.2,
badEventReduction: 0.15
},
buffDescriptions: ['健康恢復 +20%', '壞事件機率 -15%'],
dialogues: [
'慈悲為懷,萬物皆靈',
'好好照顧,觀音會保佑',
'心善之人,處處有福'
],
icon: 'deity-guanyin'
}
]

159
data/events.js Normal file
View File

@ -0,0 +1,159 @@
// 事件配置(資料驅動)
export const EVENT_CONFIG = [
// Good 事件
{
id: 'lucky_find',
type: 'good',
weight: 0.3,
condition: (state) => state.happiness > 50 && state.luck > 10,
effects: [
{ type: 'modifyStats', payload: { happiness: +10, luck: +5 } },
{ type: 'addBuff', payload: { id: 'fortune', name: '福運加持', durationTicks: 5, percent: { luck: 0.2 } } },
{ type: 'logMessage', payload: '寵物撿到符咒!好運來了~' }
]
},
{
id: 'find_treat',
type: 'good',
weight: 0.25,
condition: (state) => state.hunger < 80,
effects: [
{ type: 'modifyStats', payload: { hunger: +15, happiness: +5 } },
{ type: 'logMessage', payload: '寵物發現了美味的點心!' }
]
},
{
id: 'playful_moment',
type: 'good',
weight: 0.2,
condition: (state) => !state.isSleeping && state.happiness < 90,
effects: [
{ type: 'modifyStats', payload: { happiness: +12, dex: +1 } },
{ type: 'logMessage', payload: '寵物玩得很開心,敏捷度提升了!' }
]
},
{
id: 'deity_blessing',
type: 'good',
weight: 0.15,
condition: (state) => {
const currentDeity = state.currentDeityId
return state.deityFavors[currentDeity] > 30
},
effects: [
{ type: 'modifyStats', payload: { health: +10, happiness: +8 } },
{ type: 'addBuff', payload: { id: 'blessing', name: '神明祝福', durationTicks: 10, percent: { health: 0.1 } } },
{ type: 'logMessage', payload: '感受到神明的祝福,寵物精神煥發!' }
]
},
{
id: 'rare_treasure',
type: 'rare',
weight: 0.1,
condition: (state) => state.luck > 20 && Math.random() > 0.7,
effects: [
{ type: 'modifyStats', payload: { luck: +10, str: +2, int: +2, dex: +2 } },
{ type: 'addBuff', payload: { id: 'treasure_luck', name: '寶物運', durationTicks: 15, percent: { dropRate: 0.3 } } },
{ type: 'logMessage', payload: '✨ 發現稀有寶物!運勢大爆發!' }
]
},
// Bad 事件
{
id: 'over_eat',
type: 'bad',
weight: 0.4,
condition: (state) => state.weight > 600 && state.hunger < 20,
effects: [
{ type: 'modifyStats', payload: { health: -15, weight: +20 } },
{ type: 'addBuff', payload: { id: 'hungry_penalty', name: '飢餓懲罰', durationTicks: 3, flat: { hungerDecay: 0.1 } } },
{ type: 'logMessage', payload: '寵物偷吃太多,胃痛了!' }
]
},
{
id: 'catch_cold',
type: 'bad',
weight: 0.3,
condition: (state) => state.health < 70 && !state.isSick,
effects: [
{ type: 'modifyStats', payload: { health: -10, happiness: -5 } },
{ type: 'addBuff', payload: { id: 'sick', name: '生病', durationTicks: 5, flat: { healthDecay: 0.05 } } },
{ type: 'modifyStats', payload: { isSick: true } },
{ type: 'logMessage', payload: '寵物感冒了,需要好好休息...' }
]
},
{
id: 'accident',
type: 'bad',
weight: 0.2,
condition: (state) => state.dex < 30 && Math.random() > 0.5,
effects: [
{ type: 'modifyStats', payload: { health: -8, happiness: -10 } },
{ type: 'logMessage', payload: '寵物不小心摔了一跤,受傷了...' }
]
},
{
id: 'lonely',
type: 'bad',
weight: 0.1,
condition: (state) => state.happiness < 30 && state.hunger > 50,
effects: [
{ type: 'modifyStats', payload: { happiness: -15 } },
{ type: 'logMessage', payload: '寵物感到孤單,心情低落...' }
]
},
// Weird 事件
{
id: 'stare_wall',
type: 'weird',
weight: 0.3,
condition: (state) => state.isSleeping === false && Math.random() > 0.7,
effects: [
{ type: 'modifyStats', payload: { happiness: -5 } },
{ type: 'logMessage', payload: '寵物盯著牆壁發呆... 怪怪的。' }
]
},
{
id: 'sudden_sleep',
type: 'weird',
weight: 0.25,
condition: (state) => !state.isSleeping && state.happiness < 50,
effects: [
{ type: 'modifyStats', payload: { isSleeping: true } },
{ type: 'modifyStats', payload: { health: +5 } },
{ type: 'logMessage', payload: '寵物突然睡著了... 可能是太累了?' }
]
},
{
id: 'mysterious_glow',
type: 'weird',
weight: 0.2,
condition: (state) => state.luck > 15 && Math.random() > 0.6,
effects: [
{ type: 'modifyStats', payload: { luck: +3, int: +1 } },
{ type: 'logMessage', payload: '寵物身上發出神秘的光芒... 智力提升了?' }
]
},
{
id: 'random_dance',
type: 'weird',
weight: 0.15,
condition: (state) => state.happiness > 60 && !state.isSleeping,
effects: [
{ type: 'modifyStats', payload: { happiness: +8, dex: +1 } },
{ type: 'logMessage', payload: '寵物突然開始跳舞!看起來很開心~' }
]
},
{
id: 'philosophy_moment',
type: 'weird',
weight: 0.1,
condition: (state) => state.int > 20 && Math.random() > 0.7,
effects: [
{ type: 'modifyStats', payload: { int: +2, happiness: +5 } },
{ type: 'logMessage', payload: '寵物似乎在思考人生... 智力提升了!' }
]
}
]

89
data/fortune-lots.js Normal file
View File

@ -0,0 +1,89 @@
// 籤詩資料(簡化版,實際應從 JSON 載入)
export const FORTUNE_LOTS = [
{
id: '1',
grade: '上上',
palace: '子宮',
poem1: '天開地闢萬物全 人力回天事事全',
poem2: '若問前途與運泰 唯有善德鬼神欽',
meaning: '大吉大利,萬事亨通',
explanation: '此籤象徵開天闢地之意,預示大吉',
oracle: '家宅:平安、事業:順利',
story: '宋太祖黃袍加身',
effects: {
addBuff: {
id: 'fortune_blessing',
name: '上上籤祝福',
durationTicks: 20,
percent: { luck: 0.3, dropRate: 0.25 }
}
}
},
{
id: '2',
grade: '上',
palace: '丑宮',
poem1: '鯤化為鵬海浪翻 陰陽交泰太平間',
poem2: '庶人驀有凌霄志 平地雷聲震山川',
meaning: '時來運轉,大展宏圖',
explanation: '此籤預示時機已到,可大展身手',
oracle: '家宅:興旺、事業:順利',
story: '鯤鵬展翅',
effects: {
addBuff: {
id: 'good_fortune',
name: '上籤祝福',
durationTicks: 15,
percent: { luck: 0.2 }
}
}
},
{
id: '50',
grade: '中',
palace: '午宮',
poem1: '五湖四海任君行 高掛帆篷自在撐',
poem2: '若得順風隨即至 滿船寶貝喜層層',
meaning: '順風順水,收穫豐碩',
explanation: '此籤預示順境,但需把握時機',
oracle: '家宅:平穩、事業:順利',
story: '順風行船',
effects: {
addBuff: {
id: 'normal_fortune',
name: '中籤祝福',
durationTicks: 10,
percent: { luck: 0.1 }
}
}
},
{
id: '80',
grade: '下',
palace: '申宮',
poem1: '炎炎烈火焰連天 焰裡還生一朵蓮',
poem2: '到底永成根不壞 依然生葉長枝根',
meaning: '雖有困難,終能克服',
explanation: '此籤預示雖有困難,但能化險為夷',
oracle: '家宅:需注意、事業:需努力',
story: '火中蓮',
effects: {
addBuff: {
id: 'bad_fortune',
name: '下籤影響',
durationTicks: 5,
percent: { luck: -0.1 }
}
}
}
]
// 抽籤機率配置
export const FORTUNE_GRADE_WEIGHTS = {
'上上': 0.05,
'上': 0.15,
'中': 0.40,
'下': 0.30,
'下下': 0.10
}

39
data/items.js Normal file
View File

@ -0,0 +1,39 @@
// 道具配置
export const ITEMS = {
cookie: {
id: 'cookie',
name: '幸運餅乾',
type: 'consumable',
effects: { modifyStats: { hunger: +20, happiness: +10 } },
description: '增加飢餓和快樂'
},
water: {
id: 'water',
name: '神水',
type: 'consumable',
effects: { modifyStats: { health: +15, happiness: +5 } },
description: '恢復健康'
},
amulet: {
id: 'amulet',
name: '平安符',
type: 'equipment',
effects: { addBuff: { id: 'amulet_protection', name: '平安符保護', durationTicks: Infinity, flat: { health: 20 } } },
description: '永久增加健康上限'
},
sunglasses: {
id: 'sunglasses',
name: '酷酷墨鏡',
type: 'equipment',
effects: { addBuff: { id: 'cool', name: '酷炫', durationTicks: Infinity, percent: { happiness: 0.1 } } },
description: '永久增加快樂 10%'
},
training_manual: {
id: 'training_manual',
name: '訓練手冊',
type: 'equipment',
effects: { addBuff: { id: 'training', name: '訓練加成', durationTicks: Infinity, percent: { str: 0.15, dex: 0.15 } } },
description: '永久增加力量和敏捷成長'
}
}

24
data/pet-species.js Normal file
View File

@ -0,0 +1,24 @@
// 寵物種族配置
export const PET_SPECIES = {
tinyTigerCat: {
id: 'tinyTigerCat',
name: '小虎斑貓',
description: '活潑可愛的小貓咪',
baseStats: {
hungerDecayPerTick: 0.05,
happinessDecayPerTick: 0.08,
poopChancePerTick: 0.02,
sicknessChance: 0.01,
dropRate: 0.1,
luck: 10
},
lifecycle: [
{ stage: 'egg', durationSeconds: 0, unlockedFeatures: [] },
{ stage: 'baby', durationSeconds: 1800, unlockedFeatures: [] },
{ stage: 'child', durationSeconds: 3600, unlockedFeatures: ['miniGames'] },
{ stage: 'adult', durationSeconds: Infinity, unlockedFeatures: ['miniGames', 'breeding'] }
],
personality: ['活潑', '黏人']
}
}

335
docs/API_STRUCTURE.md Normal file
View File

@ -0,0 +1,335 @@
# API 端點結構設計
本文檔定義了虛擬寵物系統的 API 端點結構,用於前端與後端整合。
## 基礎配置
- **Base URL**: `http://localhost:3000/api` (開發環境)
- **認證**: 未來可加入 JWT Token
- **資料格式**: JSON
## API 端點列表
### 1. 寵物狀態相關
#### GET `/pet/state`
獲取當前寵物狀態
**Response:**
```json
{
"speciesId": "tinyTigerCat",
"stage": "baby",
"hunger": 80.5,
"happiness": 60.2,
"health": 100,
"weight": 500,
"ageSeconds": 120,
"poopCount": 2,
"str": 5,
"int": 3,
"dex": 8,
"luck": 15,
"isSleeping": false,
"isSick": false,
"isDead": false,
"currentDeityId": "mazu",
"deityFavors": {
"mazu": 20,
"earthgod": 10,
"yuelao": 0,
"wenchang": 0,
"guanyin": 5
},
"dailyPrayerCount": 1,
"destiny": null,
"buffs": [],
"inventory": [],
"generation": 1,
"lastTickTime": 1234567890
}
```
#### POST `/pet/update`
更新寵物狀態(部分更新)
**Request Body:**
```json
{
"hunger": 85,
"happiness": 70
}
```
**Response:**
```json
{
"success": true,
"data": { /* 完整狀態物件 */ }
}
```
#### POST `/pet/save`
儲存完整寵物狀態
**Request Body:**
```json
{ /* 完整 PetState 物件 */ }
```
**Response:**
```json
{
"success": true,
"message": "狀態已儲存"
}
```
---
### 2. 事件系統相關
#### GET `/events/list`
獲取所有事件配置
**Response:**
```json
[
{
"id": "lucky_find",
"type": "good",
"weight": 0.3,
"condition": "function_string_or_null",
"effects": [
{
"type": "modifyStats",
"payload": { "happiness": 10, "luck": 5 }
}
]
}
]
```
#### POST `/events/trigger`
觸發事件
**Request Body:**
```json
{
"eventId": "lucky_find",
"petState": { /* 當前狀態 */ }
}
```
**Response:**
```json
{
"success": true,
"eventId": "lucky_find",
"effects": [
{
"type": "modifyStats",
"updates": { "happiness": 10, "luck": 5 }
}
]
}
```
---
### 3. Buff 系統相關
#### POST `/buffs/apply`
應用 Buff
**Request Body:**
```json
{
"id": "fortune",
"name": "福運加持",
"durationTicks": 5,
"percent": { "luck": 0.2 }
}
```
**Response:**
```json
{
"success": true,
"buff": { /* Buff 物件 */ }
}
```
---
### 4. 神明系統相關
#### GET `/deities/list`
獲取所有神明資料
**Response:**
```json
[
{
"id": "mazu",
"name": "媽祖",
"personality": "溫柔守護",
"buffs": {
"gameSuccessRate": 0.1,
"sicknessReduction": 0.15
},
"buffDescriptions": ["小遊戲 +10%", "生病機率 -15%"],
"dialogues": ["好孩子,媽祖保佑你平安喔"],
"icon": "deity-mazu"
}
]
```
#### POST `/deities/pray`
祈福
**Request Body:**
```json
{
"deityId": "mazu",
"petState": { /* 當前狀態 */ }
}
```
**Response:**
```json
{
"success": true,
"favorIncrease": 5,
"newFavor": 25,
"message": "祈福成功"
}
```
---
### 5. 籤詩系統相關
#### POST `/fortune/draw`
抽籤
**Request Body:**
```json
{
"deityId": "guanyin" // 可選
}
```
**Response:**
```json
{
"success": true,
"lot": {
"id": "1",
"grade": "上上",
"palace": "子宮",
"poem1": "天開地闢萬物全 人力回天事事全",
"poem2": "若問前途與運泰 唯有善德鬼神欽",
"meaning": "大吉大利,萬事亨通",
"explanation": "此籤象徵開天闢地之意,預示大吉",
"oracle": "家宅:平安、事業:順利",
"story": "宋太祖黃袍加身"
},
"buff": {
"id": "fortune_blessing",
"name": "上上籤祝福",
"durationTicks": 20,
"percent": { "luck": 0.3, "dropRate": 0.25 }
}
}
```
---
### 6. 道具系統相關
#### GET `/items/list`
獲取所有道具
**Response:**
```json
[
{
"id": "cookie",
"name": "幸運餅乾",
"type": "consumable",
"effects": {
"modifyStats": { "hunger": 20, "happiness": 10 }
},
"description": "增加飢餓和快樂"
}
]
```
#### POST `/items/use`
使用道具
**Request Body:**
```json
{
"itemId": "cookie",
"count": 1
}
```
**Response:**
```json
{
"success": true,
"itemId": "cookie",
"count": 1,
"effects": {
"modifyStats": { "hunger": 20, "happiness": 10 }
}
}
```
---
## 錯誤處理
所有 API 錯誤應返回以下格式:
```json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "錯誤訊息"
}
}
```
常見錯誤碼:
- `PET_NOT_FOUND`: 寵物不存在
- `INVALID_STATE`: 狀態無效
- `EVENT_CONDITION_FAILED`: 事件條件不滿足
- `DAILY_LIMIT_EXCEEDED`: 每日限制已達上限
- `ITEM_NOT_FOUND`: 道具不存在
- `INSUFFICIENT_ITEM`: 道具數量不足
---
## 實作建議
### 後端實作
1. 使用 RESTful API 設計
2. 實作資料驗證Joi/Yup
3. 加入認證中介層(未來)
4. 實作速率限制rate limiting
5. 使用資料庫儲存狀態PostgreSQL/MongoDB
### 前端整合
1. 使用 `ApiService` 類別統一管理 API 呼叫
2. 實作錯誤處理和重試機制
3. 使用狀態管理Pinia/Vuex同步狀態
4. 實作樂觀更新Optimistic Updates
### Mock 模式
開發階段可使用 `ApiService` 的 mock 模式,所有資料儲存在 `localStorage`,方便測試和開發。

276
docs/USAGE.md Normal file
View File

@ -0,0 +1,276 @@
# 虛擬寵物系統 - 使用說明
## 🚀 快速開始
### 方式一:瀏覽器 Console推薦
1. 啟動 Nuxt 開發伺服器:
```bash
npm run dev
```
2. 打開瀏覽器,訪問:`http://localhost:3000/console-demo.html`
3. 打開開發者工具F12 或 Cmd+Option+I切換到 **Console** 標籤
4. 系統會自動初始化,然後你可以輸入命令:
```javascript
// 查看幫助
help()
// 顯示狀態
showStatus()
// 啟動遊戲循環
start()
// 餵食
feed(20)
// 玩耍
play(15)
// 祈福
pray()
// 抽籤
drawFortune()
```
### 方式二Node.js 環境
```bash
node console-demo.js
```
## 📁 專案結構
```
nuxt-app/
├── data/ # 資料配置(資料驅動)
│ ├── pet-species.js # 寵物種族配置
│ ├── deities.js # 神明資料
│ ├── events.js # 事件配置
│ ├── items.js # 道具配置
│ └── fortune-lots.js # 籤詩資料
├── core/ # 核心系統
│ ├── api-service.js # API 服務層(支援 mock/real
│ ├── pet-system.js # 寵物系統
│ ├── event-system.js # 事件系統
│ └── temple-system.js # 神明系統
├── console-demo.js # Console 互動介面
├── public/
│ └── console-demo.html # 瀏覽器版本
└── docs/
├── API_STRUCTURE.md # API 端點文檔
└── USAGE.md # 本文件
```
## 🎮 可用命令
### 遊戲控制
- `init()` - 初始化系統
- `start()` - 啟動遊戲循環(每 3 秒 tick每 10 秒檢查事件)
- `stop()` - 停止遊戲循環
- `showStatus()` - 顯示當前寵物狀態
### 寵物互動
- `feed(amount)` - 餵食(預設 +20 飢餓)
- `play(amount)` - 玩耍(預設 +15 快樂,+0.5 敏捷)
- `clean()` - 清理便便(+10 快樂)
- `heal(amount)` - 治療(預設 +20 健康,可治癒疾病)
- `sleep()` - 睡覺/起床(睡覺時 +5 健康)
### 事件系統
- `triggerEvent(eventId)` - 手動觸發事件(測試用)
- `listEvents()` - 查看所有可用事件
- `history()` - 查看事件歷史記錄
- `applyBuffs()` - 手動應用 Buff通常自動執行
### 神明系統
- `pray()` - 祈福(每日上限 3 次,+5 好感度)
- `drawFortune()` - 抽籤(獲得 Buff
- `switchDeity(deityId)` - 切換神明('mazu', 'earthgod', 'yuelao', 'wenchang', 'guanyin'
- `listDeities()` - 查看所有神明及其好感度
## 🔌 API 整合
### 切換到真實 API
`console-demo.js` 或使用時修改:
```javascript
import { ApiService } from './core/api-service.js'
const apiService = new ApiService({
useMock: false, // 切換到真實 API
baseUrl: 'https://your-api-domain.com/api',
mockDelay: 0
})
```
### API 端點結構
詳見 `docs/API_STRUCTURE.md`,包含:
- 寵物狀態相關端點
- 事件系統端點
- Buff 系統端點
- 神明系統端點
- 籤詩系統端點
- 道具系統端點
## 📊 資料結構
### PetState寵物狀態
```javascript
{
speciesId: 'tinyTigerCat',
stage: 'baby',
hunger: 80,
happiness: 60,
health: 100,
weight: 500,
ageSeconds: 0,
str: 0,
int: 0,
dex: 0,
luck: 15,
isSleeping: false,
isSick: false,
isDead: false,
currentDeityId: 'mazu',
deityFavors: { mazu: 20, ... },
dailyPrayerCount: 0,
buffs: [],
inventory: []
}
```
### GameEvent事件配置
```javascript
{
id: 'lucky_find',
type: 'good',
weight: 0.3,
condition: (state) => state.happiness > 50,
effects: [
{ type: 'modifyStats', payload: { happiness: +10 } },
{ type: 'addBuff', payload: { ... } }
]
}
```
### BuffBuff 效果)
```javascript
{
id: 'fortune',
name: '福運加持',
durationTicks: 5,
flat: { hunger: -10 }, // 固定值加成
percent: { luck: 0.2 } // 百分比加成
}
```
## 🎯 核心機制
### Tick 循環
- 每 3 秒執行一次
- 自動衰減:飢餓 -5%,快樂 -8%
- 便便機率2%
- 生病機率1%
### 事件觸發
- 每 10 秒檢查一次
- 10% 機率觸發
- 依權重隨機選擇事件
- 檢查條件函數
- 執行效果(修改屬性、添加 Buff 等)
### Buff 系統
- 每 tick 自動應用
- 計算公式:`(base + flat) * (1 + percent)`
- 持續時間倒數Infinity 為永久)
- 可堆疊(需設定 `stacks: true`
### 神明系統
- 5 位神明:媽祖、土地公、月老、文昌、觀音
- 每日祈福上限 3 次
- 好感度 0-100每 20 點一星
- 不同神明有不同 Buff 加成
## 🛠️ 擴展開發
### 添加新事件
編輯 `data/events.js`
```javascript
{
id: 'new_event',
type: 'good',
weight: 0.2,
condition: (state) => state.luck > 20,
effects: [
{ type: 'modifyStats', payload: { luck: +5 } },
{ type: 'logMessage', payload: '新事件觸發!' }
]
}
```
### 添加新道具
編輯 `data/items.js`
```javascript
newItem: {
id: 'new_item',
name: '新道具',
type: 'consumable',
effects: { modifyStats: { health: +30 } },
description: '恢復健康'
}
```
### 添加新神明
編輯 `data/deities.js`
```javascript
{
id: 'new_deity',
name: '新神明',
personality: '個性描述',
buffs: { gameSuccessRate: 0.15 },
buffDescriptions: ['小遊戲 +15%'],
dialogues: ['對話 1', '對話 2'],
icon: 'deity-new'
}
```
## 🐛 除錯技巧
1. **查看狀態**`showStatus()`
2. **查看事件歷史**`history()`
3. **手動觸發事件**`triggerEvent('lucky_find')`
4. **檢查 Buff**`eventSystem.getBuffManager().getActiveBuffs()`
5. **檢查 API 模式**`apiService.useMock`
## 📝 注意事項
1. **Mock 模式**:資料儲存在 `localStorage`,刷新頁面會保留
2. **API 同步**:所有狀態更新都會嘗試同步到 API失敗時降級到本地
3. **事件條件**:條件函數必須返回 boolean
4. **Buff 計算**:每 tick 自動應用,無需手動呼叫(除非測試)
5. **每日限制**:祈福次數每日重置(需實作時間檢查)
## 🔮 未來擴展
- [ ] 實作真實 API 後端
- [ ] 加入認證系統JWT
- [ ] 實作多人互動
- [ ] 加入小遊戲系統
- [ ] 實作繁殖系統
- [ ] 加入成就系統
- [ ] 實作排行榜

6
eslint.config.mjs Normal file
View File

@ -0,0 +1,6 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
// Your custom configs here
)

15
nuxt.config.ts Normal file
View File

@ -0,0 +1,15 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxt/content',
'@nuxt/eslint',
'@nuxt/hints',
'@nuxt/image',
'@nuxt/scripts',
'@nuxt/test-utils',
'@nuxt/ui'
]
})

17946
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "nuxt-app",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/content": "^3.8.2",
"@nuxt/eslint": "^1.10.0",
"@nuxt/hints": "^1.0.0-alpha.2",
"@nuxt/image": "^2.0.0",
"@nuxt/scripts": "^0.13.0",
"@nuxt/test-utils": "^3.20.1",
"@nuxt/ui": "^4.2.1",
"@unhead/vue": "^2.0.19",
"better-sqlite3": "^12.4.6",
"eslint": "^9.39.1",
"nuxt": "^4.2.1",
"typescript": "^5.9.3",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
}
}

230
public/console-demo.html Normal file
View File

@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>虛擬寵物系統 - Console 互動版</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #4ec9b0;
margin-bottom: 10px;
border-bottom: 2px solid #4ec9b0;
padding-bottom: 10px;
}
.info {
background: #252526;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
border-left: 4px solid #007acc;
}
.info h2 {
color: #4ec9b0;
margin-bottom: 10px;
}
.console-area {
background: #1e1e1e;
border: 2px solid #3c3c3c;
border-radius: 5px;
padding: 15px;
min-height: 400px;
max-height: 600px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.command-list {
background: #252526;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
}
.command-list h3 {
color: #4ec9b0;
margin-bottom: 10px;
}
.command-list code {
color: #ce9178;
background: #1e1e1e;
padding: 2px 6px;
border-radius: 3px;
}
.command-list ul {
list-style: none;
padding-left: 0;
}
.command-list li {
margin: 8px 0;
padding-left: 20px;
}
.status-display {
background: #252526;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
border-left: 4px solid #4ec9b0;
}
.warning {
color: #f48771;
background: #3c1e1e;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success {
color: #4ec9b0;
}
</style>
</head>
<body>
<div class="container">
<h1>🐾 虛擬寵物系統 - Console 互動版</h1>
<div class="info">
<h2>📖 使用說明</h2>
<p>1. 打開瀏覽器的開發者工具F12 或 Cmd+Option+I</p>
<p>2. 切換到 <strong>Console</strong> 標籤</p>
<p>3. 系統會自動初始化,然後你可以在 console 中輸入命令</p>
<p>4. 輸入 <code>help()</code> 查看所有可用命令</p>
<p>5. 輸入 <code>start()</code> 開始遊戲循環</p>
</div>
<div class="warning">
⚠️ <strong>注意:</strong>此版本使用 Mock API資料儲存在 localStorage未來可切換到真實 API。
</div>
<div class="status-display">
<h3>📊 系統狀態</h3>
<p id="system-status">正在載入...</p>
</div>
<div class="command-list">
<h3>🎮 快速命令參考</h3>
<ul>
<li><code>init()</code> - 初始化系統</li>
<li><code>showStatus()</code> - 顯示寵物狀態</li>
<li><code>start()</code> - 啟動遊戲循環</li>
<li><code>stop()</code> - 停止遊戲循環</li>
<li><code>feed(20)</code> - 餵食</li>
<li><code>play(15)</code> - 玩耍</li>
<li><code>clean()</code> - 清理便便</li>
<li><code>heal(20)</code> - 治療</li>
<li><code>pray()</code> - 祈福</li>
<li><code>drawFortune()</code> - 抽籤</li>
<li><code>help()</code> - 查看完整幫助</li>
</ul>
</div>
<div class="console-area" id="console-output">
<div class="success">✅ 系統載入中...</div>
</div>
</div>
<script type="module">
// 動態載入模組(使用絕對路徑,從專案根目錄)
const basePath = window.location.origin
const modulePath = basePath + '/console-demo.js'
let init, showStatus, start, stop, help
// 載入模組
import(modulePath).then(module => {
init = module.init
showStatus = module.showStatus
start = module.start
stop = module.stop
help = module.help
// 掛載到 window 供 console 使用
window.init = init
window.showStatus = showStatus
window.start = start
window.stop = stop
window.help = help
// 初始化系統
initializeSystem()
}).catch(error => {
console.error('載入模組失敗:', error)
updateStatus('❌ 載入模組失敗,請確認路徑正確')
})
// 更新狀態顯示
function updateStatus(message) {
const statusEl = document.getElementById('system-status')
const outputEl = document.getElementById('console-output')
if (statusEl) statusEl.textContent = message
if (outputEl) {
const div = document.createElement('div')
div.className = 'success'
div.textContent = message
outputEl.appendChild(div)
outputEl.scrollTop = outputEl.scrollHeight
}
}
// 初始化系統
async function initializeSystem() {
try {
updateStatus('正在初始化系統...')
await init()
updateStatus('✅ 系統初始化完成!輸入 help() 查看所有命令')
// 覆蓋 console.log 以顯示在頁面上
const originalLog = console.log
console.log = function(...args) {
originalLog.apply(console, args)
const outputEl = document.getElementById('console-output')
if (outputEl) {
const div = document.createElement('div')
div.textContent = args.join(' ')
outputEl.appendChild(div)
outputEl.scrollTop = outputEl.scrollHeight
}
}
} catch (error) {
updateStatus('❌ 初始化失敗: ' + error.message)
console.error(error)
}
}
// 等待 DOM 載入
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
// 模組載入完成後會自動初始化
})
}
</script>
</body>
</html>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-Agent: *
Disallow:

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}