diff --git a/pages-game/game/minesweeper/play.scss b/pages-game/game/minesweeper/play.scss index c4a5b7f..2d44489 100644 --- a/pages-game/game/minesweeper/play.scss +++ b/pages-game/game/minesweeper/play.scss @@ -447,10 +447,172 @@ color: $accent-cyan; } -.log-effect .log-content { - color: $brand-primary; +// ===================================== +// 动画特效库 +// ===================================== + +// 飘字上浮动画 +@keyframes floatUp { + 0% { + transform: translate(-50%, 0); + opacity: 0; + } + + 20% { + opacity: 1; + } + + 80% { + opacity: 1; + } + + 100% { + transform: translate(-50%, -60rpx); + opacity: 0; + } } +.float-label { + position: absolute; + pointer-events: none; + font-weight: 900; + font-size: 36rpx; + z-index: 100; + animation: floatUp 1s ease-out forwards; + text-shadow: 0 0 10rpx rgba(0, 0, 0, 0.8), 2rpx 2rpx 0 #000; + white-space: nowrap; + + &.text-heal { + color: $color-success; + } + + &.text-damage { + color: $color-error; + } + + &.text-item { + color: $color-warning; + } + + &.global { + position: fixed; + font-size: 48rpx; + z-index: 9999; + } +} + +// 屏幕震动 +@keyframes screenShake { + + 0%, + 100% { + transform: translate(0); + } + + 10% { + transform: translate(-8rpx, 6rpx); + } + + 20% { + transform: translate(8rpx, -6rpx); + } + + 30% { + transform: translate(-6rpx, -6rpx); + } + + 40% { + transform: translate(6rpx, 6rpx); + } + + 50% { + transform: translate(-6rpx, 4rpx); + } + + 60% { + transform: translate(6rpx, -4rpx); + } + + 70% { + transform: translate(-4rpx, 6rpx); + } + + 80% { + transform: translate(4rpx, -6rpx); + } + + 90% { + transform: translate(-2rpx, 2rpx); + } +} + +.screen-shake { + animation: screenShake 0.4s ease-in-out; +} + +// 玩家受伤红闪 +@keyframes damageFlash { + + 0%, + 100% { + box-shadow: none; + background: $bg-dark-card; + } + + 25%, + 75% { + background: rgba($color-error, 0.2); + box-shadow: 0 0 20rpx rgba($color-error, 0.4), inset 0 0 15rpx rgba($color-error, 0.2); + } + + 50% { + background: rgba($color-error, 0.4); + box-shadow: 0 0 30rpx rgba($color-error, 0.6), inset 0 0 20rpx rgba($color-error, 0.3); + } +} + +.player-damaged { + animation: damageFlash 0.6s ease-in-out, shake 0.4s ease-in-out; +} + +// 玩家治疗绿闪 +@keyframes healGlow { + + 0%, + 100% { + box-shadow: none; + } + + 50% { + box-shadow: 0 0 20rpx rgba($color-success, 0.6), inset 0 0 10rpx rgba($color-success, 0.3); + } +} + +.player-healed { + animation: healGlow 0.6s ease-in-out; +} + +// 倒计时紧急脉冲 +@keyframes urgentPulse { + + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + + 50% { + opacity: 0.5; + transform: scale(1.1); + } +} + +.urgent { + animation: urgentPulse 0.5s ease-in-out infinite; + color: $color-error !important; +} + + .log-empty { height: 100%; display: flex; @@ -526,7 +688,7 @@ // 对手栏 // ===================================== .opponents-bar { - height: 180rpx; + height: 150rpx; background: rgba(0, 0, 0, 0.2); border-bottom: 1px solid $border-dark; } @@ -538,20 +700,79 @@ } .player-card { - min-width: 260rpx; - height: 140rpx; + // 默认样式 (通用) background: $bg-dark-card; border: 1px solid $border-dark; border-radius: $radius-lg; - padding: $spacing-md; + padding: $spacing-sm; display: flex; align-items: center; position: relative; transition: all $transition-normal; + // 对手样式 (紧凑纵向) + &.opponent { + min-width: 140rpx; + height: 120rpx; + flex-direction: column; + justify-content: center; + gap: 6rpx; + + .avatar { + margin-right: 0; + width: 60rpx; + height: 60rpx; + font-size: 36rpx; + margin-bottom: 4rpx; + } + + .player-info { + align-items: center; + width: 100%; + } + + .username { + font-size: 20rpx; // 更小的字体 + max-width: 120rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + } + + .status-icons { + position: absolute; + top: 6rpx; + right: 6rpx; + flex-direction: column; // 图标纵向排列以节省横向空间 + gap: 2rpx; + + .icon { + font-size: 18rpx; + background: rgba(0, 0, 0, 0.5); + border-radius: 50%; + padding: 2rpx; + } + } + } + + // "我"的样式 (保持横向,但更精致) &.me { background: rgba($brand-primary, 0.1); border-color: rgba($brand-primary, 0.3); + min-width: 280rpx; + height: 140rpx; + padding: $spacing-md; + + .avatar { + width: 80rpx; + height: 80rpx; + font-size: 44rpx; + } + + .username { + font-size: $font-sm; + } } &.active-turn { @@ -560,71 +781,75 @@ } &.damaged { - animation: shake 0.5s ease-in-out; - } -} - -@keyframes shake { - - 0%, - 100% { - transform: translateX(0); + animation: damageFlash 0.6s ease-in-out, shake 0.5s ease-in-out; } - 25% { - transform: translateX(-5rpx); + &.healed { + animation: healGlow 0.6s ease-in-out; } - 75% { - transform: translateX(5rpx); + @keyframes shake { + + 0%, + 100% { + transform: translateX(0); + } + + 25% { + transform: translateX(-5rpx); + } + + 75% { + transform: translateX(5rpx); + } } -} -.avatar { - font-size: 44rpx; - margin-right: $spacing-md; - display: flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.1); - border-radius: 50%; - width: 80rpx; - height: 80rpx; + .avatar { + font-size: 44rpx; + margin-right: $spacing-md; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + width: 80rpx; + height: 80rpx; - &.lg { - width: 100rpx; - height: 100rpx; - font-size: 64rpx; + &.lg { + width: 100rpx; + height: 100rpx; + font-size: 64rpx; + } } -} -.player-info { - display: flex; - flex-direction: column; - gap: 4rpx; -} + .player-info { + display: flex; + flex-direction: column; + gap: 4rpx; + } -.username { - font-size: $font-sm; - color: $text-dark-main; - font-weight: 600; -} + .username { + font-size: $font-sm; + color: $text-dark-main; + font-weight: 600; + } -.hp-bar { - display: flex; - gap: 2rpx; -} + .hp-bar { + display: flex; + gap: 2rpx; + } -.heart { - font-size: 18rpx; -} + .heart { + font-size: 18rpx; + } -.status-icons { - display: flex; - gap: 4rpx; + .status-icons { + display: flex; + gap: 4rpx; - .icon { - font-size: 16rpx; + .icon { + font-size: 16rpx; + } } } @@ -637,7 +862,7 @@ flex-direction: column; align-items: center; justify-content: center; - padding: $spacing-lg; + padding: $spacing-md; } .turn-indicator { @@ -744,297 +969,472 @@ &.has-magnifier { border: 2rpx dashed rgba($accent-cyan, 0.5); } -} -.cell-icon { - font-size: 32rpx; -} + // --- 新增特效样式 --- + &.explosion { + animation: explode 0.5s ease-out; + } -.cell-num { - font-size: $font-lg; - font-weight: 900; -} + // 道具特效 - 移植自 Explosion.css + &.item-medkit { + animation: itemReveal 0.5s ease-out, pulseGlowGreen 2s infinite; + background-color: rgba(16, 185, 129, 0.2) !important; + } -.magnifier-mark { - display: flex; - justify-content: center; - align-items: center; - position: relative; + &.item-revive { + animation: itemReveal 0.5s ease-out, pulseGlowGreen 1.5s infinite; + background-color: rgba(16, 185, 129, 0.3) !important; + border: 1px solid #10b981; + } - .magnifier-content { - font-size: 28rpx; + &.item-knife { + animation: itemReveal 0.5s ease-out, glint 1s infinite; + background-color: rgba(239, 68, 68, 0.1) !important; + } + + &.item-lightning { + animation: itemReveal 0.4s ease-out, glint 0.5s infinite; + background-color: rgba(239, 68, 68, 0.2) !important; + } + + &.item-poison { + animation: itemReveal 0.6s ease-out, pulseGlowPurple 3s infinite; + background-color: rgba(139, 92, 246, 0.2) !important; + } + + &.item-shield { + animation: itemReveal 0.5s ease-out, pulseGlowBlue 2s infinite; + background-color: rgba(59, 130, 246, 0.2) !important; + border-radius: 50% !important; + } + + &.item-skip { + animation: itemReveal 0.5s ease-out, pulseGlowGold 2s infinite; + background-color: rgba(245, 158, 11, 0.2) !important; opacity: 0.8; } - .magnifier-badge { - position: absolute; - top: 2rpx; - right: 2rpx; - font-size: 16rpx; - } -} - -// ===================================== -// 底部面板 -// ===================================== -.bottom-panel { - padding: $spacing-md; - padding-bottom: calc($spacing-md + env(safe-area-inset-bottom)); - background: rgba(0, 0, 0, 0.4); - display: flex; - gap: $spacing-md; - border-top: 1px solid $border-dark; - height: 240rpx; // 增加底部面板高度以容纳更大的卡片 -} - -.game-logs { - flex: 1; - height: 100%; - padding: $spacing-sm; - background: rgba(0, 0, 0, 0.3); - border-radius: $radius-md; -} - -.log-line { - font-size: $font-xxs; - line-height: 1.6; - color: $text-dark-sub; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &.log-system { - color: $accent-cyan; + &.item-magnifier { + animation: itemReveal 0.5s ease-out, float 2s ease-in-out infinite; + background-color: rgba(59, 130, 246, 0.1) !important; } - &.log-effect { - color: $brand-primary; + &.item-bomb_timer { + animation: itemReveal 0.5s ease-out, pulseGlowRed 0.5s infinite, shake 0.2s infinite; + background-color: rgba(239, 68, 68, 0.3) !important; } -} -// ===================================== -// 弹窗 -// ===================================== -.modal-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10rpx); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} + &.item-curse { + animation: itemReveal 0.7s ease-out, pulseGlowPurple 1s infinite; + background-color: rgba(139, 92, 246, 0.3) !important; + filter: grayscale(0.5) contrast(1.5); + } -.modal-content { - width: 520rpx; - padding: $spacing-xxl $spacing-xl; - display: flex; - flex-direction: column; - align-items: center; - gap: $spacing-lg; -} + &.item-chest { + animation: itemReveal 0.5s ease-out, pulseGlowGold 3s infinite; + background-color: rgba(245, 158, 11, 0.3) !important; + } -.modal-emoji { - font-size: 100rpx; -} + // 补充缺失的动画 Keyframes + @keyframes explode { + 0% { + transform: scale(1); + opacity: 1; + } -.modal-title { - font-size: 40rpx; - font-weight: 900; - color: $text-dark-main; -} + 50% { + transform: scale(1.5); + opacity: 0.8; + box-shadow: 0 0 20rpx #ef4444, 0 0 40rpx #fb923c; + } -// ===================================== -// 玩法说明弹窗 -// ===================================== -.guide-modal { - width: 90%; - max-width: 640rpx; - max-height: 80vh; - display: flex; - flex-direction: column; -} + 100% { + transform: scale(1); + opacity: 1; + box-shadow: 0 0 10rpx rgba(239, 68, 68, 0.4); + } + } -.guide-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: $spacing-lg; - border-bottom: 1px solid $border-dark; -} + @keyframes itemReveal { + 0% { + transform: scale(0.5) rotate(-20deg); + opacity: 0; + } -.guide-title { - font-size: $font-lg; - font-weight: 800; - color: $text-dark-main; -} + 60% { + transform: scale(1.2) rotate(10deg); + } -.close-btn { - width: 56rpx; - height: 56rpx; - display: flex; - justify-content: center; - align-items: center; - font-size: $font-lg; - color: $text-dark-sub; - background: rgba(255, 255, 255, 0.05); - border-radius: 50%; -} + 100% { + transform: scale(1) rotate(0); + opacity: 1; + } + } -.guide-body { - padding: $spacing-lg; - max-height: 60vh; -} + @keyframes pulseGlowGreen { -.section-title { - display: block; - font-size: $font-md; - font-weight: 700; - color: $brand-primary; - margin-bottom: $spacing-md; -} + 0%, + 100% { + box-shadow: 0 0 5rpx #10b981; + } -.guide-grid { - display: flex; - flex-direction: column; - gap: $spacing-md; -} + 50% { + box-shadow: 0 0 20rpx #10b981, 0 0 30rpx #34d399; + } + } -.guide-item { - display: flex; - align-items: flex-start; - gap: $spacing-md; - padding: $spacing-sm; - background: rgba(255, 255, 255, 0.03); - border-radius: $radius-md; + @keyframes pulseGlowBlue { - .icon { + 0%, + 100% { + box-shadow: 0 0 5rpx #3b82f6; + } + + 50% { + box-shadow: 0 0 20rpx #3b82f6, 0 0 30rpx #60a5fa; + } + } + + @keyframes pulseGlowPurple { + + 0%, + 100% { + box-shadow: 0 0 5rpx #8b5cf6; + } + + 50% { + box-shadow: 0 0 20rpx #8b5cf6, 0 0 30rpx #a78bfa; + } + } + + @keyframes pulseGlowRed { + + 0%, + 100% { + box-shadow: 0 0 5rpx #ef4444; + } + + 50% { + box-shadow: 0 0 20rpx #ef4444, 0 0 30rpx #f87171; + } + } + + @keyframes pulseGlowGold { + + 0%, + 100% { + box-shadow: 0 0 5rpx #f59e0b; + } + + 50% { + box-shadow: 0 0 20rpx #f59e0b, 0 0 30rpx #fbbf24; + } + } + + @keyframes glint { + 0% { + filter: brightness(1); + } + + 50% { + filter: brightness(1.8) contrast(1.2); + } + + 100% { + filter: brightness(1); + } + } + + .cell-icon { font-size: 32rpx; - flex-shrink: 0; } - .desc { + .cell-num { + font-size: $font-lg; + font-weight: 900; + } + + .magnifier-mark { + display: flex; + justify-content: center; + align-items: center; + position: relative; + + .magnifier-content { + font-size: 28rpx; + opacity: 0.8; + } + + .magnifier-badge { + position: absolute; + top: 2rpx; + right: 2rpx; + font-size: 16rpx; + } + } + + // ===================================== + // 底部面板 + // ===================================== + .bottom-panel { + padding: $spacing-sm; + padding-bottom: calc($spacing-sm + env(safe-area-inset-bottom)); + background: rgba(0, 0, 0, 0.4); + display: flex; + gap: $spacing-md; + border-top: 1px solid $border-dark; + height: 180rpx; // 减小高度 + } + + .game-logs { + flex: 1; + height: 100%; + padding: $spacing-sm; + background: rgba(0, 0, 0, 0.3); + border-radius: $radius-md; + } + + .log-line { + font-size: $font-xxs; + line-height: 1.6; + color: $text-dark-sub; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &.log-system { + color: $accent-cyan; + } + + &.log-effect { + color: $brand-primary; + } + } + + // ===================================== + // 弹窗 + // ===================================== + .modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10rpx); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + } + + .modal-content { + width: 520rpx; + padding: $spacing-xxl $spacing-xl; display: flex; flex-direction: column; - gap: 4rpx; + align-items: center; + gap: $spacing-lg; } - .name { - font-size: $font-sm; - font-weight: 600; + .modal-emoji { + font-size: 100rpx; + } + + .modal-title { + font-size: 40rpx; + font-weight: 900; color: $text-dark-main; } - .detail { - font-size: $font-xs; + // ===================================== + // 玩法说明弹窗 + // ===================================== + .guide-modal { + width: 90%; + max-width: 640rpx; + max-height: 80vh; + display: flex; + flex-direction: column; + } + + .guide-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: $spacing-lg; + border-bottom: 1px solid $border-dark; + } + + .guide-title { + font-size: $font-lg; + font-weight: 800; + color: $text-dark-main; + } + + .close-btn { + width: 56rpx; + height: 56rpx; + display: flex; + justify-content: center; + align-items: center; + font-size: $font-lg; color: $text-dark-sub; - line-height: 1.5; - } -} - -// ===================================== -// 飘字动画 -// ===================================== -.float-label { - position: absolute; - font-size: $font-md; - font-weight: 900; - pointer-events: none; - animation: floatUp 1s ease-out forwards; - z-index: 100; - text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8); - - &.global { - position: fixed; + background: rgba(255, 255, 255, 0.05); + border-radius: 50%; } - &.text-damage { - color: $color-error; + .guide-body { + padding: $spacing-lg; + max-height: 60vh; } - &.text-heal { - color: $color-success; + .section-title { + display: block; + font-size: $font-md; + font-weight: 700; + color: $brand-primary; + margin-bottom: $spacing-md; } - &.text-effect { - color: $accent-cyan; - } -} - -@keyframes floatUp { - 0% { - opacity: 1; - transform: translateY(0) scale(1); + .guide-grid { + display: flex; + flex-direction: column; + gap: $spacing-md; } - 100% { - opacity: 0; - transform: translateY(-60rpx) scale(0.8); - } -} + .guide-item { + display: flex; + align-items: flex-start; + gap: $spacing-md; + padding: $spacing-sm; + background: rgba(255, 255, 255, 0.03); + border-radius: $radius-md; -// ===================================== -// 屏幕震动 & 通用动画 -// ===================================== -.screen-shake { - animation: screenShake 0.4s ease-in-out; -} + .icon { + font-size: 32rpx; + flex-shrink: 0; + } -@keyframes screenShake { + .desc { + display: flex; + flex-direction: column; + gap: 4rpx; + } - 0%, - 100% { - transform: translateX(0); + .name { + font-size: $font-sm; + font-weight: 600; + color: $text-dark-main; + } + + .detail { + font-size: $font-xs; + color: $text-dark-sub; + line-height: 1.5; + } } - 20% { - transform: translateX(-8rpx); + // ===================================== + // 飘字动画 + // ===================================== + .float-label { + position: absolute; + font-size: $font-md; + font-weight: 900; + pointer-events: none; + animation: floatUp 1s ease-out forwards; + z-index: 100; + text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8); + + &.global { + position: fixed; + } + + &.text-damage { + color: $color-error; + } + + &.text-heal { + color: $color-success; + } + + &.text-effect { + color: $accent-cyan; + } } - 40% { - transform: translateX(8rpx); + @keyframes floatUp { + 0% { + opacity: 1; + transform: translateY(0) scale(1); + } + + 100% { + opacity: 0; + transform: translateY(-60rpx) scale(0.8); + } } - 60% { - transform: translateX(-4rpx); + // ===================================== + // 屏幕震动 & 通用动画 + // ===================================== + .screen-shake { + animation: screenShake 0.4s ease-in-out; } - 80% { - transform: translateX(4rpx); - } -} + @keyframes screenShake { -.fadeInUp { - animation: fadeInUp 0.5s ease-out both; -} + 0%, + 100% { + transform: translateX(0); + } -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(40rpx); + 20% { + transform: translateX(-8rpx); + } + + 40% { + transform: translateX(8rpx); + } + + 60% { + transform: translateX(-4rpx); + } + + 80% { + transform: translateX(4rpx); + } } - to { - opacity: 1; - transform: translateY(0); - } -} - -.pulse { - animation: pulse 2s ease-in-out infinite; -} - -@keyframes pulse { - - 0%, - 100% { - transform: scale(1); - opacity: 0.9; + .fadeInUp { + animation: fadeInUp 0.5s ease-out both; } - 50% { - transform: scale(1.03); - opacity: 1; + @keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(40rpx); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + .pulse { + animation: pulse 2s ease-in-out infinite; + } + + @keyframes pulse { + + 0%, + 100% { + transform: scale(1); + opacity: 0.9; + } + + 50% { + transform: scale(1.03); + opacity: 1; + } } } \ No newline at end of file diff --git a/pages-game/game/minesweeper/play.vue b/pages-game/game/minesweeper/play.vue index 3754257..f621d36 100644 --- a/pages-game/game/minesweeper/play.vue +++ b/pages-game/game/minesweeper/play.vue @@ -156,12 +156,13 @@ v-for="(cell, i) in gameState.grid" :key="i" class="grid-cell" - :class="{ - 'revealed': cell.revealed, - 'type-bomb': cell.revealed && cell.type === 'bomb', - 'bg-slate-800': !cell.revealed, - 'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i] - }" + :class="[ + cell.revealed ? (cell.type === 'bomb' ? 'type-bomb explosion' : (cell.type === 'item' ? 'type-item item-' + (cell.itemId || 'generic') : 'type-empty')) : 'bg-slate-800', + { + 'revealed': cell.revealed, + 'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i] + } + ]" @tap="handleCellClick(i)" > @@ -555,6 +556,11 @@ export default { console.log('[我的位置]', data.turnOrder?.indexOf(this.myUserId)); console.log('[网格大小]', data.gridSize); console.log('[游戏状态]', data); + console.log('[DEBUG] myUserId:', this.myUserId); + console.log('[DEBUG] turnOrder:', data.turnOrder); + console.log('[DEBUG] currentTurnIndex:', data.currentTurnIndex); + console.log('[DEBUG] 当前回合玩家:', data.turnOrder?.[data.currentTurnIndex]); + console.log('[DEBUG] isMyTurn?', data.turnOrder?.[data.currentTurnIndex] === this.myUserId); console.log('================================'); } else if (opCode === 6) { // ========== 控制台日志 - 游戏结束 ========== @@ -565,13 +571,42 @@ export default { console.log('================================'); } - if (opCode === 1 || opCode === 2) { + if (opCode === 1) { + this.gameState = data; + this.addLog('system', '战局开始,准备翻格!'); + } else if (opCode === 2) { + // 状态更新 - 检测 HP 变化触发特效 + const prevState = this.gameState; + if (prevState && prevState.players) { + Object.keys(data.players || {}).forEach(uid => { + const prevP = prevState.players[uid]; + const newP = data.players[uid]; + if (!prevP || !newP) return; + + const hpDiff = newP.hp - prevP.hp; + if (hpDiff < 0) { + // 受伤 + const dmg = Math.abs(hpDiff); + this.spawnLabel(0, 0, `-${dmg}`, 'damage', undefined, uid); + this.triggerDamageEffect(uid, dmg); + } else if (hpDiff > 0) { + // 治疗 + const heal = Math.abs(hpDiff); + this.spawnLabel(0, 0, `+${heal}`, 'heal', undefined, uid); + this.healedPlayers.push(uid); + setTimeout(() => this.healedPlayers = this.healedPlayers.filter(id => id !== uid), 600); + } + }); + } this.gameState = data; - if (opCode === 1) this.addLog('system', '战局开始,准备翻格!'); } else if (opCode === 5) { this.handleEvent(data); } else if (opCode === 6) { - this.gameState = data; + if (data.gameState) { + this.gameState = data.gameState; + } else { + this.gameState = data; + } this.addLog('system', `战局结束:${data.winnerId === this.myUserId ? '您获得了胜利!' : '很遗憾失败了'}`); } }, @@ -593,6 +628,7 @@ export default { if (event.type === 'damage' || event.type === 'item') { if (iAmTarget && event.value) this.spawnLabel(0, 0, `-${event.value}`, 'damage', undefined, this.myUserId); } + // 对于 opCode 5 的 damage 事件,也触发特效(通常 opCode 2 会覆盖,但多触发一次无妨,作为保险) if (event.type === 'damage' && iAmTarget) this.triggerDamageEffect(this.myUserId, event.value); } }, @@ -661,22 +697,32 @@ export default { return this.getItemIcon(content); }, getPlayerAvatar(player) { - // 如果服务器已经发送了avatar字段,直接使用 - if (player.avatar) { - return player.avatar; + // 优先从 character 字段获取 emoji + const charAvatars = { + dog: '🐶', + elephant: '🐘', + tiger: '🐯', + monkey: '🐵', + sloth: '🦥', + hippo: '🦛', + cat: '🐱', + chicken: '🐔' + }; + if (player.character && charAvatars[player.character]) { + return charAvatars[player.character]; } - // 否则,根据userId或username确定性分配一个动物头像 - const avatars = ['🐶', '🐘', '🐯', '🐵', '🦥', '🦛']; + // 如果服务器直接发送了 avatar 字段 + if (player.avatar) return player.avatar; - // 使用userId或username作为哈希种子,确保同一玩家总是获得相同的头像 + // 降级:根据 userId 确定性分配 + const avatars = ['🐶', '🐘', '🐯', '🐵', '🦥', '🦛']; const seed = player.userId || player.username || player.id || ''; let hash = 0; for (let i = 0; i < seed.length; i++) { hash = ((hash << 5) - hash) + seed.charCodeAt(i); hash = hash & hash; // 转换为32位整数 } - const index = Math.abs(hash) % avatars.length; return avatars[index]; }, diff --git a/utils/nakamaManager.js b/utils/nakamaManager.js index e47dd6b..b0ccb28 100644 --- a/utils/nakamaManager.js +++ b/utils/nakamaManager.js @@ -340,7 +340,8 @@ class NakamaManager { } this.isConnected = false; this.session = null; - this.gameToken = null; + // 注意:不要清空 gameToken,以便重连时仍然可以使用 + // this.gameToken 只在 logout 或新 authenticate 时才会被更新 console.log('[Nakama] Disconnected'); }