Files
nl-game/src/stores/starveCoop.js
李琦 cd1ab90013 初始化
需要注意的是饥荒不够完善:动作、手持工具、细节交互、系统逻辑
2026-08-15 07:24:34 +08:00

127 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 饥荒组队联机 Store独立 WebSocket 连接 + 房间状态(建房/邀请码/座位/皮肤/准备)
// 对局内实时消息starve_input/starve_state/starve_end直接转发给游戏组件注册的回调
// 不落 pinia 响应式状态(高频快照,避免无谓的响应式开销)
import { defineStore } from 'pinia'
import { toast } from '../api/http'
// 游戏内消息回调模块级非响应式StarveGame 挂载对局时注册
let gameHandler = null
export const useStarveCoopStore = defineStore('starveCoop', {
state: () => ({
ws: null, // WebSocket 实例(独立于对战大厅的连接)
connected: false, // 连接状态
roomState: null, // 房间状态快照(座位/皮肤/准备/状态)
closedReason: '', // 房间被解散原因
pingTimer: null, // 应用层心跳
}),
getters: {
inRoom: (s) => !!s.roomState,
mySeat: (s) => s.roomState?.my_seat ?? -1,
isHost: (s) => s.roomState && s.roomState.host_id === s.roomState.seats?.[s.roomState.my_seat]?.user_id,
// 已入座的玩家列表(联机开局用)
activePlayers: (s) =>
(s.roomState?.seats || [])
.filter((seat) => seat.occupied)
.map((seat) => ({ seat: seat.index, name: seat.name, skin: seat.skin || 'wilson', userId: seat.user_id })),
},
actions: {
// 建立独立连接;带 JWT成功后 resolve
connect() {
if (this.ws && (this.ws.readyState === 0 || this.ws.readyState === 1)) {
return Promise.resolve()
}
const token = localStorage.getItem('token')
let host = location.host
let proto = location.protocol === 'https:' ? 'wss' : 'ws'
const base = window.desktop?.serverBase
if (base) {
const u = new URL(base)
host = u.host
proto = u.protocol === 'https:' ? 'wss' : 'ws'
}
const url = `${proto}://${host}/ws?token=${encodeURIComponent(token)}`
return new Promise((resolve, reject) => {
const ws = new WebSocket(url)
this.ws = ws
ws.onopen = () => {
this.connected = true
this.pingTimer = setInterval(() => this.send('ping', {}), 30000)
resolve()
}
ws.onmessage = (ev) => this.handleMessage(JSON.parse(ev.data))
ws.onclose = () => {
// 主动 disconnect 时 this.ws 已被置 null这里只处理"意外断线"
// 后端断线即移除座位,客户端必须同步清掉房间状态,否则 UI 仍显示在房间内
if (this.ws !== ws) return
this.connected = false
clearInterval(this.pingTimer)
if (this.roomState) {
this.roomState = null
gameHandler?.('room_closed', { reason: '连接已断开' })
toast('连接已断开,请重新建房或加入', 'info')
}
}
ws.onerror = () => reject(new Error('连接失败'))
})
},
handleMessage(msg) {
const { type, data } = msg
switch (type) {
case 'room_state':
this.roomState = data
gameHandler?.(type, data)
break
case 'starve_input':
case 'starve_state':
case 'starve_end':
// 对局内实时消息:直接交给游戏组件(主机收 input客机收 state全员收 end
gameHandler?.(type, data)
break
case 'room_closed':
this.closedReason = data.reason || '房间已解散'
this.roomState = null
gameHandler?.('room_closed', data)
toast(this.closedReason, 'info')
break
case 'error':
toast(data.msg || '操作失败')
break
// chat_msg / friend_event 由主连接battle store负责这里忽略避免重复处理
}
},
send(type, data = {}) {
if (this.ws?.readyState === 1) {
this.ws.send(JSON.stringify({ type, data }))
}
},
// ---- 房间操作 ----
createRoom() { this.send('create_room', { game: 'starve', mode: 'pvp' }) },
joinRoom(code) { this.send('join_room', { code: String(code || '').trim().toUpperCase() }) },
leaveRoom() {
this.send('leave_room')
this.roomState = null
},
setReady(ready) { this.send('ready', { ready }) },
setSkin(code) { this.send('skin', { skin: code }) },
startGame() { this.send('start') },
// ---- 对局内消息 ----
sendInput(data) { this.send('starve_input', data) }, // 客机 → 房主(服务端转发)
sendState(data) { this.send('starve_state', data) }, // 房主 → 其余座位(服务端广播)
sendOver() { this.send('starve_over') }, // 房主宣告全灭结束
// 游戏组件注册/注销对局消息回调
setGameHandler(cb) { gameHandler = cb },
clearGameHandler() { gameHandler = null },
// 断开连接(退出游玩页时调用)
disconnect() {
clearInterval(this.pingTimer)
const ws = this.ws
this.ws = null // 先置空再 closeonclose 回调据此识别"主动断开"并跳过断线处理
ws?.close()
this.connected = false
this.roomState = null
gameHandler = null
},
},
})