package network import ( "bufio" "encoding/binary" "io" "log" "net" "time" "gitea.boner.be/bdnugget/goonscape/game" "gitea.boner.be/bdnugget/goonscape/types" pb "gitea.boner.be/bdnugget/goonserver/actions" "google.golang.org/protobuf/proto" ) var serverAddr = "boner.be:6969" func SetServerAddr(addr string) { serverAddr = addr } func ConnectToServer() (net.Conn, int32, error) { conn, err := net.Dial("tcp", serverAddr) if err != nil { log.Printf("Failed to dial server: %v", err) return nil, 0, err } log.Println("Connected to server. Waiting for player ID...") reader := bufio.NewReader(conn) // Read message length (4 bytes) lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { log.Printf("Failed to read message length: %v", err) return nil, 0, err } messageLength := binary.BigEndian.Uint32(lengthBuf) // Read the full message messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { log.Printf("Failed to read message body: %v", err) return nil, 0, err } var response pb.ServerMessage if err := proto.Unmarshal(messageBuf, &response); err != nil { log.Printf("Failed to unmarshal server response: %v", err) return nil, 0, err } playerID := response.GetPlayerId() log.Printf("Successfully connected with player ID: %d", playerID) return conn, playerID, nil } func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player) { // Create a buffered reader for the connection reader := bufio.NewReader(conn) actionTicker := time.NewTicker(types.ClientTickRate) defer actionTicker.Stop() go func() { for range actionTicker.C { player.Lock() if len(player.ActionQueue) > 0 { actions := make([]*pb.Action, len(player.ActionQueue)) copy(actions, player.ActionQueue) batch := &pb.ActionBatch{ PlayerId: playerID, Actions: actions, Tick: player.CurrentTick, } player.ActionQueue = player.ActionQueue[:0] player.Unlock() if err := writeMessage(conn, batch); err != nil { log.Printf("Failed to send actions to server: %v", err) return } } else { player.Unlock() } } }() for { // Read message length (4 bytes) lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { log.Printf("Failed to read message length: %v", err) return } messageLength := binary.BigEndian.Uint32(lengthBuf) // Read the full message messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { log.Printf("Failed to read message body: %v", err) return } var serverMessage pb.ServerMessage if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil { log.Printf("Failed to unmarshal server message: %v", err) continue } player.Lock() player.CurrentTick = serverMessage.CurrentTick tickDiff := serverMessage.CurrentTick - player.CurrentTick if tickDiff > types.MaxTickDesync { for _, state := range serverMessage.Players { if state.PlayerId == playerID { player.ForceResync(state) break } } } player.Unlock() for _, state := range serverMessage.Players { if state.PlayerId == playerID { continue } if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { otherPlayer.UpdatePosition(state, types.ServerTickRate) } else { otherPlayers[state.PlayerId] = types.NewPlayer(state) } } if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 { g.Chat.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 }