package network import ( "bufio" "context" "encoding/binary" "fmt" "io" "log" "net" "sync" "time" "gitea.boner.be/bdnugget/goonscape/logging" "gitea.boner.be/bdnugget/goonscape/types" pb "gitea.boner.be/bdnugget/goonserver/actions" "google.golang.org/protobuf/proto" ) const protoVersion = 1 var serverAddr = "boner.be:6969" type NetworkManager struct { ctx context.Context conn net.Conn reader *bufio.Reader writer *bufio.Writer mu sync.Mutex } func NewNetworkManager(ctx context.Context) *NetworkManager { return &NetworkManager{ ctx: ctx, } } func SetServerAddr(addr string) { serverAddr = addr } func (nm *NetworkManager) Connect(addr string) error { nm.mu.Lock() defer nm.mu.Unlock() var err error nm.conn, err = net.Dial("tcp", addr) if err != nil { return err } nm.reader = bufio.NewReader(nm.conn) nm.writer = bufio.NewWriter(nm.conn) go nm.readLoop() return nil } func (nm *NetworkManager) readLoop() { for { select { case <-nm.ctx.Done(): return default: // Read and process messages } } } func (nm *NetworkManager) Send(message proto.Message) error { nm.mu.Lock() defer nm.mu.Unlock() data, err := proto.Marshal(message) if err != nil { return err } // Write length prefix lengthBuf := make([]byte, 4) binary.BigEndian.PutUint32(lengthBuf, uint32(len(data))) if _, err := nm.writer.Write(lengthBuf); err != nil { return err } // Write message body if _, err := nm.writer.Write(data); err != nil { return err } return nm.writer.Flush() } func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) { logging.Info.Println("Attempting to connect to server at", serverAddr) conn, err := net.Dial("tcp", serverAddr) if err != nil { logging.Error.Printf("Failed to dial server: %v", err) return nil, 0, err } logging.Info.Println("Connected to server. Authenticating...") // Send auth message authAction := &pb.Action{ Type: pb.Action_LOGIN, Username: username, Password: password, } if isRegistering { authAction.Type = pb.Action_REGISTER } authBatch := &pb.ActionBatch{ Actions: []*pb.Action{authAction}, ProtocolVersion: protoVersion, } if err := writeMessage(conn, authBatch); err != nil { conn.Close() return nil, 0, fmt.Errorf("failed to send auth: %v", err) } // Read server response reader := bufio.NewReader(conn) lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { conn.Close() return nil, 0, fmt.Errorf("failed to read auth response: %v", err) } messageLength := binary.BigEndian.Uint32(lengthBuf) messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { conn.Close() return nil, 0, fmt.Errorf("failed to read auth response body: %v", err) } var response pb.ServerMessage if err := proto.Unmarshal(messageBuf, &response); err != nil { conn.Close() return nil, 0, fmt.Errorf("failed to unmarshal auth response: %v", err) } if response.ProtocolVersion > protoVersion { conn.Close() return nil, 0, fmt.Errorf("server requires newer protocol version (server: %d, client: %d)", response.ProtocolVersion, protoVersion) } if !response.AuthSuccess { conn.Close() return nil, 0, fmt.Errorf("authentication failed: %s", response.ErrorMessage) } playerID := response.GetPlayerId() log.Printf("Successfully authenticated with player ID: %d", playerID) return conn, playerID, nil } func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers *sync.Map, quitChan <-chan struct{}) { defer func() { logging.Info.Println("Closing connection and cleaning up for player", playerID) conn.Close() close(player.QuitDone) }() reader := bufio.NewReader(conn) ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { select { case <-quitChan: return case <-ticker.C: // Read message length lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { log.Printf("Failed to read message length: %v", err) continue } messageLength := binary.BigEndian.Uint32(lengthBuf) // Read message body messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { log.Printf("Failed to read message body: %v", err) continue } // Process server message var serverMessage pb.ServerMessage if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil { log.Printf("Failed to unmarshal server message: %v", err) continue } // Update player states for _, state := range serverMessage.Players { if state == nil { logging.Error.Println("Received nil player state") continue } if state.PlayerId == playerID { player.UpdatePosition(state, types.ServerTickRate) } else if existing, ok := otherPlayers.Load(state.PlayerId); ok { existing.(*types.Player).UpdatePosition(state, types.ServerTickRate) } else { newPlayer := types.NewPlayer(state) otherPlayers.Store(state.PlayerId, newPlayer) } } // Handle chat messages if handler, ok := player.UserData.(types.ChatMessageHandler); ok { handler.HandleServerMessages(serverMessage.ChatMessages) } } } } // Helper function to write length-prefixed messages func writeMessage(conn net.Conn, msg proto.Message) error { data, err := proto.Marshal(msg) if err != nil { return err } // Write length prefix lengthBuf := make([]byte, 4) binary.BigEndian.PutUint32(lengthBuf, uint32(len(data))) if _, err := conn.Write(lengthBuf); err != nil { return err } // Write message body _, err = conn.Write(data) return err }