pet_data/app/app-retro.vue

574 lines
14 KiB
Vue

<template>
<div class="retro-container">
<!-- 标题栏 -->
<div class="title-bar">
<div class="title-text">[ VIRTUAL PET SYSTEM v1.0 ]</div>
<div class="system-status">{{ systemStatus }}</div>
</div>
<!-- 主游戏区域 -->
<div class="game-area" v-if="petState">
<!-- 状态显示区 -->
<div class="stats-panel">
<div class="panel-title">[ STATUS ]</div>
<div class="stat-row">
<span class="stat-name">NAME:</span>
<span class="stat-value">{{ petState.name || 'UNNAMED' }}</span>
</div>
<div class="stat-row">
<span class="stat-name">STAGE:</span>
<span class="stat-value">{{ getStageName(petState.stage) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">AGE:</span>
<span class="stat-value">{{ formatAge(petState.ageSeconds) }}</span>
</div>
<div class="divider">---------------</div>
<div class="stat-row">
<span class="stat-name">HUNGER:</span>
<span class="stat-bar-text">{{ makeBar(petState.hunger) }} {{ Math.round(petState.hunger) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">HAPPY:</span>
<span class="stat-bar-text">{{ makeBar(petState.happiness) }} {{ Math.round(petState.happiness) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">HEALTH:</span>
<span class="stat-bar-text">{{ makeBar((petState.health / getMaxHealth(petState)) * 100) }} {{ Math.round(petState.health) }}/{{ getMaxHealth(petState) }}</span>
</div>
<div class="divider">---------------</div>
<div class="stat-row">
<span class="stat-name">STR:</span>
<span class="stat-value">{{ Math.round(petState.effectiveStr || petState.str || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">INT:</span>
<span class="stat-value">{{ Math.round(petState.effectiveInt || petState.int || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">DEX:</span>
<span class="stat-value">{{ Math.round(petState.effectiveDex || petState.dex || 0) }}</span>
</div>
<div class="stat-row">
<span class="stat-name">LUCK:</span>
<span class="stat-value">{{ Math.round(petState.effectiveLuck || petState.luck || 0) }}</span>
</div>
<div class="divider">---------------</div>
<div v-if="petState.isSleeping" class="status-flag">[SLEEPING]</div>
<div v-if="petState.isSick" class="status-flag sick">[SICK]</div>
<div v-if="petState.poopCount > 0" class="status-flag">[POOP x{{ petState.poopCount }}]</div>
</div>
<!-- 命令输入区 -->
<div class="command-panel">
<div class="panel-title">[ ACTIONS ]</div>
<div class="command-grid">
<button class="cmd-btn" @click="handleFeed" :disabled="!canDoAction('feed')">
[F]EED
</button>
<button class="cmd-btn" @click="handlePlay('normal')" :disabled="!canDoAction('play')">
[P]LAY
</button>
<button class="cmd-btn" @click="handleClean" :disabled="!canDoAction('clean')">
[C]LEAN
</button>
<button class="cmd-btn" @click="handleHeal" :disabled="!canDoAction('heal')">
[H]EAL
</button>
<button class="cmd-btn" @click="handleSleep" :disabled="!canDoAction('sleep')">
{{ petState?.isSleeping ? '[W]AKE' : '[S]LEEP' }}
</button>
<button class="cmd-btn" @click="handlePray" :disabled="!isReady">
P[R]AY
</button>
</div>
</div>
<!-- 控制台输出 -->
<div class="console-panel">
<div class="panel-title">[ CONSOLE ]</div>
<div class="console-log" id="console-output">
<div class="log-line">SYSTEM READY...</div>
</div>
</div>
</div>
<!-- Debug 面板 -->
<button class="debug-toggle" @click="showDebug = !showDebug">[DEBUG]</button>
<div v-if="showDebug" class="debug-panel">
<div class="panel-title">[ DEBUG TOOLS ]</div>
<button class="cmd-btn" @click="debugResetPrayer">RESET PRAYER</button>
<button class="cmd-btn" @click="debugAddFavor(50)">+50 FAVOR</button>
<button class="cmd-btn" @click="debugMaxAttributes">MAX STATS</button>
<button class="cmd-btn" @click="debugAddPoop">ADD POOP x4</button>
</div>
<!-- 名字输入 Modal -->
<div v-if="showNameInput" class="name-modal">
<div class="modal-box">
<div class="panel-title">[ NAME YOUR PET ]</div>
<input
v-model="inputPetName"
@keyup.enter="confirmName"
type="text"
placeholder="ENTER NAME..."
maxlength="20"
class="name-input"
/>
<button class="cmd-btn" @click="confirmName" :disabled="!inputPetName || inputPetName.trim().length === 0">
CONFIRM
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { PetSystem } from '../core/pet-system.js'
import { EventSystem } from '../core/event-system.js'
import { TempleSystem } from '../core/temple-system.js'
// 状态
const petState = ref(null)
const systemStatus = ref('INITIALIZING...')
const showDebug = ref(false)
const showNameInput = ref(false)
const inputPetName = ref('')
const isReady = ref(false)
const isRunning = ref(false)
// 系统实例
let petSystem = null
let eventSystem = null
let templeSystem = null
// 生成 ASCII 进度条
function makeBar(value) {
const MAX_BARS = 10
const filled = Math.round((value || 0) / 10)
return '[' + '█'.repeat(filled) + '░'.repeat(MAX_BARS - filled) + ']'
}
// 格式化年龄
function formatAge(seconds) {
if (!seconds) return '0s'
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor(seconds % 60)
if (h > 0) return `${h}h${m}m`
if (m > 0) return `${m}m${s}s`
return `${s}s`
}
// 获取阶段名称
function getStageName(stage) {
const names = {
egg: 'EGG',
baby: 'BABY',
child: 'CHILD',
adult: 'ADULT'
}
return names[stage] || stage
}
// 获取最大健康
function getMaxHealth(state) {
if (!state) return 100
const bonuses = getAllBonuses(state)
return 100 + (bonuses.health || 0)
}
// 获取加成
function getAllBonuses(state) {
if (!petSystem) return {}
return petSystem.getAllBonuses()
}
// 动作检查
function canDoAction(action) {
if (!petState.value) return false
if (action === 'feed') return petState.value.hunger < 100
if (action === 'play') return petState.value.hunger > 0 && petState.value.happiness < 100
if (action === 'clean') return petState.value.poopCount > 0
if (action === 'heal') return petState.value.health < getMaxHealth(petState.value)
if (action === 'sleep') return !petState.value.isDead
return isReady.value
}
// 动作处理
async function handleFeed() {
const result = await petSystem.feed()
log(result.success ? `FED PET. HUNGER +${result.hungerGain}` : result.message)
updateState()
}
async function handlePlay(type = 'normal') {
const result = await petSystem.play({ gameType: type })
log(result.success ? `PLAYED. HAPPY +${Math.round(result.happinessGain)}` : result.message)
updateState()
}
async function handleClean() {
const result = await petSystem.cleanPoop()
log(result.success ? `CLEANED ${result.poopCount} POOP` : result.message)
updateState()
}
async function handleHeal() {
const result = await petSystem.heal()
log(result.success ? `HEALED +${Math.round(result.healAmount)}` : result.message)
updateState()
}
async function handleSleep() {
const result = await petSystem.toggleSleep()
log(result.success ? result.message : result.message)
updateState()
}
async function handlePray() {
const result = await templeSystem.pray()
if (result.success) {
log(`PRAYED. FAVOR +${result.favorIncrease} (${result.newFavor}/100)`)
if (result.reachedMax) log('*** MAX FAVOR REACHED! ***')
} else {
log(result.message)
}
updateState()
}
// Debug 工具
async function debugResetPrayer() {
templeSystem.debugResetDailyPrayer()
log('[DEBUG] PRAYER RESET')
updateState()
}
async function debugAddFavor(amount) {
const deityId = petState.value.currentDeityId
const current = petState.value.deityFavors[deityId] || 0
await petSystem.updateState({
deityFavors: {
...petState.value.deityFavors,
[deityId]: Math.min(100, current + amount)
}
})
petSystem.calculateCombatStats()
log(`[DEBUG] FAVOR +${amount}`)
updateState()
}
async function debugMaxAttributes() {
await petSystem.debugSetStat('str', 100)
await petSystem.debugSetStat('int', 100)
await petSystem.debugSetStat('dex', 100)
log('[DEBUG] STATS MAXED')
updateState()
}
async function debugAddPoop() {
await petSystem.debugSetStat('poopCount', 4)
log('[DEBUG] ADDED 4 POOP')
updateState()
}
// 更新状态
function updateState() {
if (petSystem) {
petState.value = { ...petSystem.getState() }
}
}
// 日志
function log(message) {
const consoleEl = document.getElementById('console-output')
if (consoleEl) {
const line = document.createElement('div')
line.className = 'log-line'
line.textContent = `> ${message}`
consoleEl.appendChild(line)
consoleEl.scrollTop = consoleEl.scrollHeight
// 限制日志行数
while (consoleEl.children.length > 50) {
consoleEl.removeChild(consoleEl.firstChild)
}
}
}
// 确认名字
async function confirmName() {
if (inputPetName.value && inputPetName.value.trim().length > 0) {
await petSystem.updateState({ name: inputPetName.value.trim() })
showNameInput.value = false
log(`PET NAMED: ${inputPetName.value.trim()}`)
updateState()
}
}
// 初始化
onMounted(async () => {
log('LOADING SYSTEMS...')
petSystem = new PetSystem()
eventSystem = new EventSystem(petSystem)
templeSystem = new TempleSystem(petSystem)
await petSystem.initialize()
await eventSystem.initialize()
updateState()
// 检查是否有名字
if (!petState.value.name) {
showNameInput.value = true
}
// 启动循环
petSystem.startTickLoop()
eventSystem.startEventCheck()
isRunning.value = true
isReady.value = true
systemStatus.value = 'RUNNING'
log('SYSTEM INITIALIZED')
log('TYPE COMMANDS TO INTERACT')
})
onUnmounted(() => {
if (petSystem) petSystem.stopTickLoop()
if (eventSystem) eventSystem.stopEventCheck()
})
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
* {
font-family: 'Press Start 2P', monospace;
image-rendering: pixelated;
}
.retro-container {
background: #000;
color: #0f0;
min-height: 100vh;
padding: 20px;
font-size: 12px;
line-height: 1.8;
}
.title-bar {
border: 2px solid #0f0;
padding: 10px;
margin-bottom: 20px;
text-align: center;
}
.title-text {
font-size: 16px;
margin-bottom: 10px;
}
.system-status {
font-size: 10px;
color: #0a0;
}
.game-area {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.stats-panel,
.command-panel,
.console-panel {
border: 2px solid #0f0;
padding: 15px;
}
.panel-title {
color: #0ff;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #0f0;
}
.stat-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.stat-name {
color: #0a0;
min-width: 80px;
}
.stat-value {
color: #fff;
}
.stat-bar-text {
color: #0ff;
font-size: 10px;
}
.divider {
color: #050;
margin: 10px 0;
font-size: 8px;
}
.status-flag {
color: #ff0;
margin-top: 5px;
animation: blink 1s infinite;
}
.status-flag.sick {
color: #f00;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
.command-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.cmd-btn {
background: #000;
border: 2px solid #0f0;
color: #0f0;
padding: 10px;
cursor: pointer;
font-family: 'Press Start 2P', monospace;
font-size: 10px;
transition: all 0.1s;
}
.cmd-btn:hover:not(:disabled) {
background: #0f0;
color: #000;
}
.cmd-btn:active:not(:disabled) {
transform: translate(2px, 2px);
}
.cmd-btn:disabled {
border-color: #030;
color: #030;
cursor: not-allowed;
}
.console-panel {
grid-column: 1 / -1;
}
.console-log {
height: 150px;
overflow-y: auto;
background: #001100;
padding: 10px;
font-size: 10px;
}
.log-line {
margin-bottom: 5px;
color: #0c0;
}
.debug-toggle {
position: fixed;
top: 20px;
right: 20px;
background: #000;
border: 2px solid #f0f;
color: #f0f;
padding: 5px 10px;
cursor: pointer;
font-size: 10px;
}
.debug-panel {
position: fixed;
top: 60px;
right: 20px;
background: #000;
border: 2px solid #f0f;
padding: 15px;
max-width: 300px;
}
.debug-panel .cmd-btn {
width: 100%;
margin-bottom: 5px;
border-color: #f0f;
color: #f0f;
}
.name-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-box {
background: #000;
border: 3px solid #0ff;
padding: 30px;
min-width: 400px;
}
.name-input {
width: 100%;
background: #001100;
border: 2px solid #0f0;
color: #0f0;
padding: 10px;
font-family: 'Press Start 2P', monospace;
font-size: 12px;
margin: 20px 0;
}
.name-input:focus {
outline: none;
border-color: #0ff;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #000;
}
::-webkit-scrollbar-thumb {
background: #0f0;
border: 1px solid #000;
}
</style>