File size: 3,621 Bytes
055e3d8
 
 
 
 
 
7fb6a22
055e3d8
7fb6a22
 
 
055e3d8
 
 
7fb6a22
ea5f18a
055e3d8
 
7fb6a22
 
 
 
 
055e3d8
7fb6a22
055e3d8
7fb6a22
055e3d8
 
 
7fb6a22
 
055e3d8
 
 
 
 
 
 
 
 
7fb6a22
055e3d8
7fb6a22
ea5f18a
7dd98ed
 
 
 
055e3d8
7fb6a22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93f151d
 
28bfc7d
 
 
 
 
93f151d
28bfc7d
 
 
93f151d
7fb6a22
055e3d8
7fb6a22
 
 
 
 
055e3d8
7fb6a22
055e3d8
 
 
7fb6a22
 
055e3d8
7fb6a22
 
 
 
055e3d8
 
 
 
 
 
 
 
 
7fb6a22
 
055e3d8
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * @license
 * SPDX-License-Identifier: Apache-2.0
 */

import { useState, useRef, useCallback } from "react";
import { MatchMode } from "../types";
import { getMultiplayerSessionId } from "./useProgression";
import { Client, Room } from "@colyseus/sdk";
import { ChatMessage, MultiplayerMatchContext } from "./multiplayer/types";
import { setupRoomListeners } from "./multiplayer/listeners";

export function useMultiplayer() {
  const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
  const roomRef = useRef<Room | null>(null);
  const myPilotIdRef = useRef<string>("");

  const disconnectMultiplayer = useCallback(() => {
    if (roomRef.current) {
      try {
        roomRef.current.leave();
      } catch (err) {
        console.warn("Error leaving room:", err);
      }
      roomRef.current = null;
    }
  }, []);

  const connectMultiplayer = useCallback(
    (
      engine: MultiplayerMatchContext,
      _renderer3D: any,
      mapId: string,
      mode: MatchMode,
      nickname: string,
      skin: string,
      onLocalPlayerHit: (tgtId: string, isGround: boolean) => void,
      onMatchRejected: (reason: string) => void
    ) => {
      disconnectMultiplayer();

      const sessionId = getMultiplayerSessionId();
      engine.isMultiplayer = true;
      engine.isHost = false;

      // Discard locally-spawned bots — the server is authoritative and will
      // deliver the real roster via Colyseus state sync (onAdd).
      engine.pilots = engine.pilots.filter(p => p.id === "player");

      const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
      const client = new Client(`${protocol}${window.location.host}`);

      console.log(`[Multiplayer] Connecting to Colyseus at ${window.location.host}...`);

      client.joinOrCreate("air_combat", {
        token: sessionId,
        nickname: nickname || "Maverick",
        aircraftId: engine.pilots.find(p => p.id === "player")?.specs.id || "falcon-mk2",
        skin: skin || "default",
        mapId,
        mode
      })
      .then((room) => {
        roomRef.current = room;
        myPilotIdRef.current = room.sessionId;

        console.log(`[Multiplayer] Joined room: ${room.roomId}`);

        // Register all schema sync & message listeners from separate module
        setupRoomListeners(room, engine, setChatMessages, onLocalPlayerHit);
      })
      .catch((err) => {
        const reason: string = err?.message || String(err);
        console.error("[Multiplayer] Join failed:", reason, err);
        fetch("/api/client-error", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            context: "connectMultiplayer.catch",
            message: reason,
            stack: err?.stack || ""
          })
        }).catch(() => {});
        onMatchRejected(reason);
      });

      // Bind local player shoots callback to nothing on server auth
      engine.onProjectileSpawn = () => {};
      engine.onGroundTargetDamage = () => {};
      engine.onLocalPlayerKill = () => {};
      engine.onPlayerDamage = () => {};
    },
    [disconnectMultiplayer]
  );

  const sendChat = useCallback((text: string, nickname: string) => {
    if (roomRef.current) {
      roomRef.current.send("chat", text);
    } else {
      setChatMessages((prev) => [
        ...prev.slice(-49),
        { sender: nickname || "Cadet", text, ts: Date.now() }
      ]);
    }
  }, []);

  return {
    chatMessages,
    setChatMessages,
    connectMultiplayer,
    disconnectMultiplayer,
    sendChat,
    roomRef,
    myPilotIdRef
  };
}