完善了Web管理端

This commit is contained in:
2026-06-20 18:18:50 +08:00
parent aedc199777
commit c5f296fb0d
11 changed files with 735 additions and 82 deletions
+202 -9
View File
@@ -5,6 +5,7 @@ import (
"Embalming_Girl_Server/game"
"Embalming_Girl_Server/room"
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
@@ -25,6 +26,7 @@ type Client struct {
Username string
Nickname string
Token string
Version string
Conn *websocket.Conn
Send chan []byte
mu sync.Mutex
@@ -51,6 +53,18 @@ func (h *Hub) OnlineCount() int {
return len(h.clients)
}
func (h *Hub) OnlineClientsList() []map[string]interface{} {
h.mu.RLock()
defer h.mu.RUnlock()
var list []map[string]interface{}
for _, c := range h.clients {
list = append(list, map[string]interface{}{
"id": c.ID, "username": c.Username, "nickname": c.Nickname, "room_id": c.RoomID, "version": c.Version,
})
}
return list
}
func (h *Hub) InitTestRooms() {
for _, n := range []int{3, 4, 5, 6} {
rid := "TEST" + string(rune('0'+n))
@@ -109,15 +123,59 @@ func (h *Hub) removeClient(c *Client) {
h.mu.Lock()
delete(h.clients, c.ID)
h.mu.Unlock()
if c.RoomID != "" {
h.RoomMgr.LeaveRoom(c.RoomID, c.ID)
rm := h.RoomMgr.GetRoom(c.RoomID)
if rm != nil {
h.broadcastRoomState(rm)
h.mu.RLock()
_, hasGame := h.engines[c.RoomID]
h.mu.RUnlock()
if hasGame && c.Username != "" {
h.RoomMgr.DisconnectPlayer(c.RoomID, c.ID)
h.broadcastToRoom(c.RoomID, "player_disconnected", map[string]interface{}{
"nickname": c.Nickname, "message": c.Nickname + " 断线了",
})
log.Printf("Player %s disconnected, slot preserved, 60s takeover timer started", c.Username)
go func(roomID, playerID, nick string) {
time.Sleep(60 * time.Second)
if !h.RoomMgr.IsPlayerDisconnected(roomID, playerID) {
return
}
h.RoomMgr.SetPlayerBotControlled(roomID, playerID, true)
h.mu.RLock()
eng := h.engines[roomID]
h.mu.RUnlock()
if eng != nil {
eng.SetBotControlled(playerID, true)
}
h.broadcastToRoom(roomID, "player_bot_takeover", map[string]interface{}{
"nickname": nick, "message": nick + " 已被人机接管",
})
log.Printf("Player %s taken over by bot in room %s", nick, roomID)
if eng != nil && eng.Phase == "playing" {
cp := eng.CurrentPlayer()
if cp != nil && cp.ID == playerID {
h.processBotTurns(roomID)
}
}
}(c.RoomID, c.ID, c.Nickname)
} else {
h.RoomMgr.LeaveRoom(c.RoomID, c.ID)
rm := h.RoomMgr.GetRoom(c.RoomID)
if rm != nil {
h.broadcastRoomState(rm)
} else {
h.mu.Lock()
delete(h.engines, c.RoomID)
h.mu.Unlock()
}
}
}
close(c.Send)
log.Printf("Disconnected: %s", c.ID)
log.Printf("Disconnected: %s (%s)", c.ID, c.Username)
}
func (h *Hub) handleMessage(c *Client, msg map[string]interface{}) {
@@ -136,6 +194,8 @@ func (h *Hub) handleMessage(c *Client, msg map[string]interface{}) {
h.handleCreateRoom(c, p)
case "join_room":
h.handleJoinRoom(c, p)
case "rejoin_room":
h.handleRejoin(c, p)
case "leave_room":
h.handleLeaveRoom(c)
case "set_ready":
@@ -154,6 +214,8 @@ func (h *Hub) handleMessage(c *Client, msg map[string]interface{}) {
h.handleChat(c, p)
case "set_chaos":
h.handleSetChaos(c, p)
case "create_bot_room":
h.handleCreateBotRoom(c, p)
case "heartbeat":
h.sendTo(c, "heartbeat_ack", nil)
}
@@ -203,12 +265,20 @@ func (h *Hub) handleLogin(c *Client, p map[string]interface{}) {
c.Username = user.Username
c.Nickname = user.Nickname
c.Token = token
if v, ok := p["version"].(string); ok {
c.Version = v
}
log.Printf("User logged in: %s (%s)", user.Username, c.ID)
h.sendTo(c, "login_result", map[string]interface{}{
result := map[string]interface{}{
"success": true, "token": token,
"username": user.Username, "nickname": user.Nickname, "avatar": user.Avatar, "bio": user.Bio,
})
}
if roomID, playerID, found := h.RoomMgr.FindRoomByUsername(user.Username); found {
result["active_room"] = roomID
result["active_player_id"] = playerID
}
h.sendTo(c, "login_result", result)
}
func (h *Hub) handleUpdateProfile(c *Client, p map[string]interface{}) {
@@ -239,6 +309,41 @@ func (h *Hub) handleGetProfile(c *Client, p map[string]interface{}) {
})
}
func (h *Hub) handleRejoin(c *Client, p map[string]interface{}) {
roomID, _ := p["room_id"].(string)
oldPID, _ := p["player_id"].(string)
if roomID == "" || oldPID == "" {
h.sendTo(c, "error", map[string]interface{}{"message": "缺少参数"})
return
}
if !h.RoomMgr.ReconnectPlayer(roomID, oldPID, c.ID, c.Send) {
h.sendTo(c, "error", map[string]interface{}{"message": "无法重连到对局"})
return
}
c.RoomID = roomID
log.Printf("Player %s rejoined room %s", c.Username, roomID)
h.mu.RLock()
eng := h.engines[roomID]
h.mu.RUnlock()
if eng != nil {
eng.UpdatePlayerID(oldPID, c.ID)
eng.SetBotControlled(c.ID, false)
h.sendToChannel(c.Send, "game_start", map[string]interface{}{
"your_player_id": c.ID, "harmony_target": eng.HarmonyTarget,
"player_count": len(eng.Players), "rejoin": true,
})
snap := eng.Snapshot(c.ID)
h.sendToChannel(c.Send, "state_snapshot", snap)
}
rm := h.RoomMgr.GetRoom(roomID)
if rm != nil {
h.broadcastRoomState(rm)
}
h.sendTo(c, "rejoin_result", map[string]interface{}{"success": true, "room_id": roomID})
}
func (h *Hub) handleCreateRoom(c *Client, p map[string]interface{}) {
nick, _ := p["nickname"].(string)
mp := 4
@@ -251,6 +356,7 @@ func (h *Hub) handleCreateRoom(c *Client, p map[string]interface{}) {
rm := h.RoomMgr.CreateRoom(c.ID, nick, mp)
c.RoomID = rm.ID
rm.Players[c.ID].Send = c.Send
rm.Players[c.ID].Username = c.Username
h.sendTo(c, "room_created", map[string]interface{}{"room_id": rm.ID})
h.broadcastRoomState(rm)
}
@@ -268,6 +374,8 @@ func (h *Hub) handleJoinRoom(c *Client, p map[string]interface{}) {
}
c.RoomID = rm.ID
rm.Players[c.ID].Send = c.Send
rm.Players[c.ID].Username = c.Username
h.sendTo(c, "room_joined", map[string]interface{}{"room_id": rm.ID})
h.broadcastRoomState(rm)
}
@@ -298,6 +406,32 @@ func (h *Hub) handleSetChaos(c *Client, p map[string]interface{}) {
h.broadcastRoomState(rm)
}
func (h *Hub) handleCreateBotRoom(c *Client, p map[string]interface{}) {
count := 4
if v, ok := p["player_count"].(float64); ok {
count = int(v)
}
if count < 3 || count > 6 {
h.sendTo(c, "error", map[string]interface{}{"message": "人数必须在3-6之间"})
return
}
rid := fmt.Sprintf("BOT_%s", generateClientID()[:6])
h.RoomMgr.CreateTestRoom(rid, count)
rm, ok := h.RoomMgr.JoinRoom(rid, c.ID, c.Nickname)
if !ok {
h.sendTo(c, "error", map[string]interface{}{"message": "创建失败"})
return
}
c.RoomID = rm.ID
rm.Players[c.ID].Send = c.Send
rm.Players[c.ID].Username = c.Username
h.sendTo(c, "room_joined", map[string]interface{}{"room_id": rm.ID})
h.broadcastRoomState(rm)
log.Printf("Bot room %s created by %s (%d players)", rid, c.Username, count)
}
func (h *Hub) handleChat(c *Client, p map[string]interface{}) {
if c.RoomID == "" {
return
@@ -528,7 +662,7 @@ func (h *Hub) processBotTurns(roomID string) {
for eng.Phase == "playing" && !eng.IsEffectResolving() {
cp := eng.CurrentPlayer()
if cp == nil || !cp.IsBot {
if cp == nil || (!cp.IsBot && !cp.BotControlled) {
break
}
@@ -556,6 +690,7 @@ func (h *Hub) processBotTurns(roomID string) {
return
}
}
h.startTurnTimer(roomID)
}
func (h *Hub) handleAllExited(roomID string) {
@@ -624,7 +759,65 @@ func (h *Hub) handleAllExited(roomID string) {
h.broadcastRoomState(rm)
}
// ---- Audio relay ----
func (h *Hub) startTurnTimer(roomID string) {
h.mu.RLock()
eng := h.engines[roomID]
h.mu.RUnlock()
if eng == nil || eng.Phase != "playing" {
return
}
cp := eng.CurrentPlayer()
if cp == nil || cp.IsBot || cp.BotControlled {
return
}
turnID := eng.TurnID
go func() {
time.Sleep(30 * time.Second)
h.mu.RLock()
eng2 := h.engines[roomID]
h.mu.RUnlock()
if eng2 == nil || eng2.Phase != "playing" || eng2.TurnID != turnID {
return
}
cp2 := eng2.CurrentPlayer()
if cp2 == nil {
return
}
eng2.ActionLogs = append(eng2.ActionLogs, cp2.Nickname+" 超时,自动出牌")
action, target := eng2.BotAutoPlay(cp2.ID)
if action == "" {
return
}
eng2.ConfirmAction(cp2.ID, action, target)
h.broadcastSnapshots(roomID)
h.flushGameLogs(roomID)
if eng2.Phase == "all_exited" {
h.handleAllExited(roomID)
return
}
h.processBotTurns(roomID)
}()
}
// ---- Broadcast helpers ----
func (h *Hub) broadcastToRoom(roomID, msgType string, data map[string]interface{}) {
rm := h.RoomMgr.GetRoom(roomID)
if rm == nil {
return
}
for _, pid := range rm.PlayerOrder {
p := rm.Players[pid]
if p.IsBot || p.Send == nil {
continue
}
h.sendToChannel(p.Send, msgType, data)
}
}
func (h *Hub) flushGameLogs(roomID string) {
h.mu.RLock()