diff -ruN a/pkg/game/state_menu.go b/pkg/game/state_menu.go --- a/pkg/game/state_menu.go +++ b/pkg/game/state_menu.go @@ -86,28 +86,7 @@ }, ) startGameButton.Hover = true - y += startGameButton.Image().Bounds().Dy() * 3 - networkButton := data.NewButton( - x, - y, - lang.NetworkGame, - func() { - s.game.SetState(&NetworkMenuState{ - game: s.game, - }) - }, - ) - networkButton.Hover = true - y += networkButton.Image().Bounds().Dy() * 3 - exitButton := data.NewButton( - x, - y, - lang.Exit, - func() { - s.shouldQuit = true - }, - ) - exitButton.Hover = true + // FredyGames: no network game and no exit button in the web build. // Use buttons for credits. x = world.ScreenWidth - world.ScreenWidth/6 @@ -126,12 +105,9 @@ y, "kettek", func() { - OpenFile("https://kettek.net") }, ) credits1bButton.Bold = true - credits1bButton.Underline = true - credits1bButton.Hover = true y += credits1bButton.Image().Bounds().Dy() * 3 credits2aButton := data.NewButton( @@ -150,12 +126,9 @@ y, "liqMix", func() { - OpenFile("https://liq.mx") }, ) credits2bButton.Bold = true - credits2bButton.Underline = true - credits2bButton.Hover = true y += credits2bButton.Image().Bounds().Dy() * 3 credits3aButton := data.NewButton( @@ -171,12 +144,9 @@ y, "Amaruuk", func() { - OpenFile("https://birdtooth.studio") }, ) credits3bButton.Bold = true - credits3bButton.Underline = true - credits3bButton.Hover = true // Create our flag lang clickables, set them in bottom left corner x = int(float64(world.ScreenWidth) * 0.05) @@ -202,8 +172,6 @@ s.buttons = []*data.Button{ startGameButton, - networkButton, - exitButton, credits1aButton, credits1bButton, credits2aButton, @@ -281,8 +249,8 @@ // Draw left-hand ebiten op = ebiten.DrawImageOptions{} op.GeoM.Translate(float64(world.ScreenWidth)/7, float64(world.ScreenHeight)/4) - op.GeoM.Translate(-float64(s.ebitenImage.Bounds().Dx())/2, -float64(s.ebitenImage.Bounds().Dy())/2) - screen.DrawImage(s.ebitenImage, &op) + // FredyGames: the logo of the game jam is not drawn. + _ = op // Draw game buttons for _, button := range s.buttons { diff -ruN a/pkg/net/connection.go b/pkg/net/connection.go --- a/pkg/net/connection.go +++ b/pkg/net/connection.go @@ -1,544 +1,71 @@ package net -import ( - "encoding/json" - "errors" - "fmt" - "log" - "net" - "os" - "strconv" - "strings" - "sync" - "time" -) +import "errors" -type Connection struct { - Name string - - // handshakerAddr is the target handshaker service. - handshakerAddr *net.UDPAddr +// FredyGames: the networked co-op was removed from the web build. Connection keeps the methods the game calls but never opens a +// socket and never talks to a handshaker, a multicast group or another player: a Connection is never active, connected or hosting, +// every attempt to connect fails and nothing is ever sent or received. - // conn is our own base connection. - conn *net.UDPConn +var errNoNetwork = errors.New("networking is not available") - // otherConn is the peer we wish to play with. - otherConn *net.UDPConn - otherAddress *net.UDPAddr - OtherName string - multicastConn *net.UDPConn - - // - connected bool - disconnected bool - active bool - hosting bool - - // - lastReceived time.Time - lastSent time.Time - - // - messages chan Message - reliableID int - sendingReliables []ReliableTypedMessage - receivedReliables []ReliableTypedMessage - confirmedReliableIDs []int - reliableLock sync.Mutex +type Connection struct { + Name string + OtherName string } func NewConnection(name string) Connection { - return Connection{ - Name: name, - messages: make(chan Message, 1000), - active: true, - } + return Connection{Name: name} } // Connected returns if the connection is actually connected. func (c *Connection) Connected() bool { - return c.connected + return false } -// Connected returns if the connection was disconnected. +// Disconnected returns if the connection was disconnected. func (c *Connection) Disconnected() bool { - return c.disconnected + return false } // Active returns if the connection should be connected. func (c *Connection) Active() bool { - return c.active + return false } // Hosting returns if the connection is acting as a host. func (c *Connection) Hosting() bool { - return c.hosting + return false } func (c *Connection) Close() { - if c.conn != nil { - c.conn.Close() - } - if c.otherConn != nil { - c.otherConn.Close() - } - if c.multicastConn != nil { - c.multicastConn.Close() - } - c.active = false - c.hosting = false - c.connected = false - c.disconnected = false } func (c *Connection) AwaitHandshake(handshaker string, local string, target string) error { - handshakerAddr, err := net.ResolveUDPAddr("udp", handshaker) - if err != nil { - return err - } - - // Get a random local port. - localAddr, err := net.ResolveUDPAddr("udp", local) - if err != nil { - return err - } - log.Printf("Attempting to listen on %s\n", localAddr.String()) - - // Start listening! - localConn, err := net.ListenUDP("udp", localAddr) - if err != nil { - return err - } - - c.handshakerAddr = handshakerAddr - c.conn = localConn - fmt.Println("listening on", localConn.LocalAddr().String()) - - localConn.SetDeadline(time.Now().Add(time.Duration(10) * time.Second)) - - log.Println("Sending register message to handshaker service") - _, err = localConn.WriteTo([]byte(fmt.Sprintf("%d %s", RegisterMessage, c.Name)), c.handshakerAddr) - if err != nil { - return err - } - - // Wait for a character string response. - for { - buffer := make([]byte, 2) - c.conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - bytesRead, fromAddr, err := c.conn.ReadFromUDP(buffer) - if err != nil { - return err - } - // Ignore sends from non-handhsaker. - if fromAddr.String() != handshakerAddr.String() { - continue - } - c.conn.SetReadDeadline(time.Time{}) - msg := string(buffer[0:bytesRead]) - a, err := strconv.Atoi(msg) - if err != nil { - return err - } - if a != int(HandshakerMessage) { - return errors.New("incorrect handshake response") - } - break - } - - if target != "" { - log.Printf("Sending await message for %s to handshaker service\n", target) - _, err := localConn.WriteTo([]byte(fmt.Sprintf("%d %s", AwaitMessage, target)), c.handshakerAddr) - if err != nil { - return err - } - } else { - c.hosting = true - } - - return c.awaitHandshake() -} - -func (c *Connection) awaitHandshake() error { - fmt.Println("entering main await") - for { - buffer := make([]byte, 1024) - bytesRead, fromAddr, err := c.conn.ReadFromUDP(buffer) - if err != nil { - return err - } - msg := string(buffer[0:bytesRead]) - parts := strings.Split(msg, " ") - a, err := strconv.Atoi(parts[0]) - if err != nil { - return err - } - if a == int(ArrivedMessage) { - otherAddr, err := net.ResolveUDPAddr("udp", parts[1]) - if err != nil { - return err - } - _, err = c.conn.WriteTo([]byte(fmt.Sprintf("%d %s", HelloMessage, c.Name)), otherAddr) - if err != nil { - return err - } - c.otherAddress = otherAddr - return nil - } else if a == int(HelloMessage) { - c.otherAddress = fromAddr - return nil - } else { - fmt.Println("unhandled message from", fromAddr.String()) - continue - } - } + return errNoNetwork } -// AwaitDirect attempts to set up a client-to-client connection without any handshaking. func (c *Connection) AwaitDirect(local string, target string) error { - localAddr, err := net.ResolveUDPAddr("udp", local) - if err != nil { - return err - } - log.Printf("Attempting to listen on %s\n", localAddr.String()) - - // Start listening! - localConn, err := net.ListenUDP("udp", localAddr) - if err != nil { - return err - } - - c.conn = localConn - fmt.Println("listening on", localConn.LocalAddr().String()) - - if target != "" { - otherAddr, err := net.ResolveUDPAddr("udp", target) - if err != nil { - return err - } - _, err = c.conn.WriteTo([]byte(fmt.Sprintf("%d %s", HelloMessage, c.Name)), otherAddr) - if err != nil { - return err - } - } else { - c.hosting = true - } - - // Start the listen loop. - for { - buffer := make([]byte, 1024) - bytesRead, fromAddr, err := c.conn.ReadFromUDP(buffer) - if err != nil { - return err - } - msg := string(buffer[0:bytesRead]) - parts := strings.Split(msg, " ") - a, err := strconv.Atoi(parts[0]) - if err != nil { - return err - } - if a == int(HelloMessage) { - fmt.Println("got hello from self-declared", parts[1]) - fmt.Println(fromAddr.String()) - - // Send hello back to let the other client that we're ready to rumble. - _, err := c.conn.WriteTo([]byte(fmt.Sprintf("%d %s", HelloMessage, c.Name)), fromAddr) - if err != nil { - return err - } - - c.otherAddress = fromAddr - break - } else { - // BOGUS - } - } - return nil + return errNoNetwork } func (c *Connection) AwaitLAN(joiner bool) error { - localAddr, err := net.ResolveUDPAddr("udp4", "") - if err != nil { - return err - } - - // Start listening! - localConn, err := net.ListenUDP("udp4", localAddr) - if err != nil { - return err - } - - c.conn = localConn - fmt.Println("listening on", localConn.LocalAddr().String()) - - multiAddr, err := net.ResolveUDPAddr("udp4", "239.0.0.0:20221") - if err != nil { - return err - } - if !joiner { - c.multicastConn, err = net.DialUDP("udp4", nil, multiAddr) - if err != nil { - return err - } - fmt.Println("broadcasting on", c.multicastConn.LocalAddr().String()) - c.hosting = true - } else { - c.multicastConn, err = net.ListenMulticastUDP("udp4", nil, multiAddr) - if err != nil { - return err - } - fmt.Println("listening for broadcast on", c.multicastConn.LocalAddr().String()) - } - - // Start the listen loop. - for { - if !joiner { - buffer := make([]byte, 1024) - c.conn.SetReadDeadline(time.Now().Add(1 * time.Second)) - bytesRead, fromAddr, err := c.conn.ReadFromUDP(buffer) - if err != nil && !os.IsTimeout(err) { - return err - } - if os.IsTimeout(err) { - c.multicastConn.Write([]byte(fmt.Sprintf("%d %s", BroadcastMessage, c.conn.LocalAddr()))) - } else { - msg := string(buffer[0:bytesRead]) - parts := strings.Split(msg, " ") - a, err := strconv.Atoi(parts[0]) - if err != nil { - return err - } - if a == int(HelloMessage) { - c.otherAddress = fromAddr - c.multicastConn.Close() - c.multicastConn = nil - break - } - } - // 1. Await localConn for msg - // 2. If nothing, send broadcast of localConn - // 3. Otherwise, send hello on localConn back to sender. - } else { - // 1. Await multicastConn for msg - // 2. - buffer := make([]byte, 1024) - c.conn.SetReadDeadline(time.Now().Add(1 * time.Second)) - bytesRead, fromAddr, err := c.multicastConn.ReadFromUDP(buffer) - if err != nil && !os.IsTimeout(err) { - return err - } - if os.IsTimeout(err) { - // - } else { - msg := string(buffer[0:bytesRead]) - - parts := strings.Split(msg, " ") - a, err := strconv.Atoi(parts[0]) - if err != nil { - return err - } - if a == int(BroadcastMessage) { - targetAddr, err := net.ResolveUDPAddr("udp", parts[1]) - if err != nil { - continue - } - // Let's construct a correct address by joining the multicast ip to the target port. - targetAddr.IP = fromAddr.IP - - _, err = c.conn.WriteTo([]byte(fmt.Sprintf("%d %s", HelloMessage, c.Name)), targetAddr) - if err != nil { - return err - } - c.otherAddress = targetAddr - - c.multicastConn.Close() - c.multicastConn = nil - - break - } - } - } - } - return nil + return errNoNetwork } func (c *Connection) Loop() { - fmt.Println("starting main loop with", c.otherAddress.String()) - if err := c.Send(HenloMessage{ - Name: c.Name, - Greeting: "hai", - }); err != nil { - panic(err) - } - c.connected = true - c.lastReceived = time.Now() - c.lastSent = time.Now() - for { - t := time.Now() - // More than 5 seconds have passed since last receive, presume failure. - if t.Sub(c.lastReceived) > 5*time.Second { - fmt.Println("lost connection") - c.connected = false - c.disconnected = true - return - } - // Send a ping every 3 seconds. - if t.Sub(c.lastSent) > 3*time.Second { - c.Send(PingMessage{}) - } - - // Attempt to read any pending messages, with a 2 second deadline. - b := make([]byte, 1000) - c.conn.SetReadDeadline(t.Add(2 * time.Second)) - n, foreignAddr, err := c.conn.ReadFromUDP(b) - if err != nil && !os.IsTimeout(err) { - fmt.Println("disconnect") - c.connected = false - fmt.Println(err) - return - } - if foreignAddr.String() != c.otherAddress.String() { - continue - } - b = b[:n] - var msg ReliableTypedMessage - if err = json.Unmarshal(b, &msg); err != nil { - fmt.Println(err) - } else { - c.lastReceived = time.Now() - // Handle reliable messages such that we send a response whenever we receive it. - if msg.InboundID != 0 { - c.reliableLock.Lock() - // See if we've already confirmed sending this particular message. - for _, id := range c.confirmedReliableIDs { - if id == msg.InboundID { - c.reliableLock.Unlock() - // Ignore it if we've already confirmed it. - goto loopEnd - } - } - for i, m := range c.sendingReliables { - if m.OutboundID == msg.InboundID { - c.confirmedReliableIDs = append(c.confirmedReliableIDs, m.OutboundID) - c.sendingReliables = append(c.sendingReliables[:i], c.sendingReliables[i+1:]...) - c.reliableLock.Unlock() - goto loopEnd - } - } - c.reliableLock.Unlock() - } else if msg.OutboundID != 0 { - c.reliableLock.Lock() - // Always send an empty response with the ID. - c.sendReliableWithIDs(nil, msg.OutboundID, 0) - // Only continue processing if we haven't processed this specific message yet. - for _, m := range c.receivedReliables { - if m.OutboundID == msg.InboundID { - c.reliableLock.Unlock() - goto loopEnd - } - } - c.receivedReliables = append(c.receivedReliables, msg) - c.reliableLock.Unlock() - } - switch m := msg.Message().(type) { - case HenloMessage: - c.OtherName = m.Name - case PingMessage: - default: - c.messages <- m - } - } - loopEnd: - // This is a bit of a bad spot for this, but resend any unconfirmed reliable messages at this point. - // FIXME: We should have some sort of "back off" algorithm that adjusts the time to wait to resend in according to time last received, so as to prevent spamming a d/ced connection into oblivion. - for _, m := range c.sendingReliables { - n := time.Now() - if n.Sub(m.lastSent) >= 500*time.Millisecond { - c.reliableLock.Lock() - c.sendReliableWithIDs(m.Message(), 0, m.OutboundID) - c.reliableLock.Unlock() - m.lastSent = n - } - } - } } -// Send sends the given message interface to the other player. +// Send does nothing: there is no other player. func (c *Connection) Send(msg Message) error { - var envelope TypedMessage - - payload, err := json.Marshal(msg) - if err != nil { - return err - } - - envelope.Type = msg.Type() - envelope.Data = payload - - bytes, err := json.Marshal(envelope) - if err != nil { - return err - } - - if bytes != nil { - _, err = c.conn.WriteTo(bytes, c.otherAddress) - } - c.lastSent = time.Now() - return err + return errNoNetwork } -// SendReliable sends the given message with special resending until a confirmation is received. +// SendReliable does nothing: there is no other player. func (c *Connection) SendReliable(msg Message) error { - c.reliableLock.Lock() - c.reliableID++ - env, err := c.sendReliableWithIDs(msg, 0, c.reliableID) - env.lastSent = time.Now() - c.sendingReliables = append(c.sendingReliables, env) - c.reliableLock.Unlock() - return err -} - -func (c *Connection) sendReliableWithIDs(msg Message, inboundID, outboundID int) (ReliableTypedMessage, error) { - var envelope ReliableTypedMessage - - payload, err := json.Marshal(msg) - if err != nil { - return envelope, err - } - - if msg != nil { - envelope.Type = msg.Type() - } - envelope.Data = payload - envelope.InboundID = inboundID - envelope.OutboundID = outboundID - - bytes, err := json.Marshal(envelope) - if err != nil { - return envelope, err - } - - if bytes != nil { - _, err = c.conn.WriteTo(bytes, c.otherAddress) - } - c.lastSent = time.Now() - return envelope, err + return errNoNetwork } -// Messages returns the current contents of the messages channel as a slice. +// Messages always returns nothing. func (c *Connection) Messages() (m []Message) { - for { - done := false - select { - case msg := <-c.messages: - m = append(m, msg) - default: - done = true - break - } - if done { - break - } - } - return m + return nil }