package network import ( "bufio" "encoding/binary" "fmt" "io" "log" "net" "time" "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(username, password string, isRegistering bool) (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. 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}, } 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.AuthSuccess { conn.Close() return nil, 0, fmt.Errorf(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 map[int32]*types.Player, quitChan <-chan struct{}) { reader := bufio.NewReader(conn) actionTicker := time.NewTicker(types.ClientTickRate) defer actionTicker.Stop() defer conn.Close() defer close(player.QuitDone) // Create a channel to signal when goroutines are done done := make(chan struct{}) go func() { for { select { case <-quitChan: // Send disconnect message to server disconnectMsg := &pb.ActionBatch{ PlayerId: playerID, Actions: []*pb.Action{{ Type: pb.Action_DISCONNECT, PlayerId: playerID, }}, } writeMessage(conn, disconnectMsg) done <- struct{}{} return case <-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 { select { case <-quitChan: done := make(chan struct{}) go func() { <-done close(player.QuitDone) }() select { case <-done: time.Sleep(100 * time.Millisecond) case <-time.After(1 * time.Second): log.Println("Shutdown timed out") } return default: // 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 handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { 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 }