diff -ruN a/assets/locales/en.yaml b/assets/locales/en.yaml --- a/assets/locales/en.yaml +++ b/assets/locales/en.yaml @@ -3,7 +3,7 @@ "Hat": "Hat" "Input": "Input" "Loading": "Customizing your experience...\nthis may take ~40 seconds..." -"WaitingPlayer": "Press to Join\nor\nWait for network player..." +"WaitingPlayer": "Press to Join" "Morte": "Morte" "ResetRoom": " to restart room" "Quit": " to quit" diff -ruN a/assets/locales/ja.yaml b/assets/locales/ja.yaml --- a/assets/locales/ja.yaml +++ b/assets/locales/ja.yaml @@ -3,7 +3,7 @@ "Hat": "帽子" "Input": "入力" "Loading": "あなたの体験をカスタマイズしています...\nこれには約40秒かかるかもしれません..." -"WaitingPlayer": "<スタート>を押して参加\nまたは\nネットワークプレイヤーを待つ..." +"WaitingPlayer": "<スタート>を押して参加" "Morte": "モルテ" "ResetRoom": "<エンター>を押して部屋を再開する" "Quit": "を押して終了する" diff -ruN a/net/peer.go b/net/peer.go --- a/net/peer.go +++ b/net/peer.go @@ -1,124 +1,18 @@ package net -import ( - "fmt" - "net" - "sync" - "time" +import "errors" - "github.com/xtaci/kcp-go" -) +var errNoNetwork = errors.New("networking is not available") type Peer struct { - id uint32 - addr *net.UDPAddr // Address of the peer - conn *net.UDPConn // Pointer to the serverclient's conn. - session *kcp.UDPSession - // Packet reading. - packetBuffer []byte - readLock sync.Mutex - readReadyChan chan bool -} - -type PeerPacket struct { - peer *Peer - msg Message -} - -func NewPeer(addr *net.UDPAddr, conn *net.UDPConn) *Peer { - return &Peer{ - addr: addr, - conn: conn, - readReadyChan: make(chan bool, NetChannelSize), - } + id uint32 } func (p *Peer) ID() uint32 { return p.id } -// writeToPacketBuffer is used internally to write from the single UDP connection to a virtual packet buffer for use by the Peer. -func (p *Peer) writeToPacketBuffer(b []byte) { - p.readLock.Lock() - p.packetBuffer = append(p.packetBuffer, b...) - p.readLock.Unlock() - p.readReadyChan <- true -} - -func (p *Peer) loop(ch chan PeerPacket) { - for { - b := make([]byte, NetBufferSize) - n, err := p.session.Read(b) - - msg, _ := MessageFromBytes(b[:n]) - if msg == nil { - fmt.Println("unknown message ID:", b[0], "passing as raw message.") - msg, _ = MessageRaw{}.FromBytes(b[:n]) - } - - if err != nil { - fmt.Println(err) - return - } - ch <- PeerPacket{ - peer: p, - msg: msg, - } - } -} - -// Send sends a Payload to the given peer. +// Send always fails: there is no network. func (p *Peer) Send(msg Message) error { - if p.session == nil { - return fmt.Errorf("no sesssion") - } - //p.conn.WriteTo(msg.ToBytes(), p.addr) - p.session.Write(msg.ToBytes()) - //p.encoder.Encode(&msg) - return nil -} - -// ReadFrom is used to read from the peer's virtual packet buffer. -func (p *Peer) ReadFrom(b []byte) (n int, addr net.Addr, err error) { - <-p.readReadyChan - addr = p.addr - p.readLock.Lock() - n = copy(b, p.packetBuffer) - p.packetBuffer = p.packetBuffer[n:] - p.readLock.Unlock() - return -} - -// WriteTo writes the bytes to the given address. -func (p *Peer) WriteTo(b []byte, addr net.Addr) (n int, err error) { - n, err = p.conn.WriteTo(b, addr) - return -} - -// Close closes the peer connection. (it actually does nothing!) -func (p *Peer) Close() error { - return nil -} - -// LocalAddr returns the connection's local address. -func (p *Peer) LocalAddr() net.Addr { - return p.addr -} - -// SetDeadline does nothing. -func (p *Peer) SetDeadline(t time.Time) error { - fmt.Println("TODO: Peer.SetDeadline") - return nil -} - -// SetReadDeadline does nothing. -func (p *Peer) SetReadDeadline(t time.Time) error { - fmt.Println("TODO: Peer.SetReadDeadline") - return nil -} - -// SetWriteDeadline does nothing. -func (p *Peer) SetWriteDeadline(t time.Time) error { - fmt.Println("TODO: Peer.SetWriteDeadline") - return nil + return errNoNetwork } diff -ruN a/net/serverclient.go b/net/serverclient.go --- a/net/serverclient.go +++ b/net/serverclient.go @@ -1,15 +1,7 @@ package net -import ( - "fmt" - "math/rand" - "net" - "os" - "strings" - "time" - - "github.com/xtaci/kcp-go" -) +// FredyGames: the networked co-op was removed from the web build. ServerClient keeps the shape the game expects but never opens +// a socket, never has peers and never reports events. var NetDataShards = 10 var NetParityShards = 3 @@ -17,217 +9,31 @@ var NetChannelSize = 10 type ServerClient struct { - id uint32 // Our ID used to advertise to others. - // - Matchmaker string - matchmakerAddr *net.UDPAddr - UseMatchmaker bool - Hosting bool - Running bool - localAddr *net.UDPAddr - localConn *net.UDPConn - closeChan chan struct{} - rawChan chan Packet - peerChan chan PeerPacket - EventChan chan Event - peers []*Peer + UseMatchmaker bool + Hosting bool + Running bool + EventChan chan Event } func (s *ServerClient) Init() { - s.id = uint32(rand.Int31()) - s.closeChan = make(chan struct{}) - s.rawChan = make(chan Packet, NetChannelSize*2) - s.peerChan = make(chan PeerPacket, NetChannelSize) s.EventChan = make(chan Event, NetChannelSize) - s.Matchmaker = "gamu.group:20220" } func (s *ServerClient) ID() uint32 { - return s.id + return 0 } func (s *ServerClient) Open(address string) error { - // Set up our matchmaker address. - matchMakerAddr, err := net.ResolveUDPAddr("udp", s.Matchmaker) - if err == nil { - s.matchmakerAddr = matchMakerAddr - } else { - fmt.Println("matchmaker's address is bad", err) - } - - // Get our UDP address. - addr, err := net.ResolveUDPAddr("udp", address) - if err != nil { - return err - } - - // Open our UDP connection. - conn, err := net.ListenUDP("udp", addr) - if err != nil { - return err - } - - s.localAddr = addr - s.localConn = conn - s.Running = true - - fmt.Println("...now listening on", conn.LocalAddr().String()) - - s.EventChan <- EventHosting{} - - go s.LogicLoop() - go s.ReadLoop() - - return nil + return errNoNetwork } func (s *ServerClient) ConnectTo(address string) error { - if address != "" && address[0] == ':' { - address = "127.0.0.1" + address - } - addr, err := net.ResolveUDPAddr("udp", address) - if err != nil { - return err - } - peer := NewPeer(addr, s.localConn) - s.peers = append(s.peers, peer) - - session, err := kcp.NewConn3(0, addr, nil, NetDataShards, NetParityShards, peer) - if err != nil { - panic(err) - } - peer.session = session - go peer.loop(s.peerChan) - - s.EventChan <- EventJoining{} - - peer.Send(MessageID{ID: s.id}) - - return nil -} - -type Packet struct { - buffer []byte - addr *net.UDPAddr - readBytes int -} - -// LogicLoop is the main logic loop that handles raw packets and otherwise. -func (s *ServerClient) LogicLoop() { - for s.Running { - select { - case <-s.closeChan: - for _, p := range s.peers { - p.session.Close() - } - s.localConn.Close() - s.Running = false - s.EventChan <- EventClosed{} - return - case msg := <-s.peerChan: - switch msg.msg.(type) { - case MessageID: - if msg.peer.id == 0 { - if !s.Hosting { - s.EventChan <- EventJoined{} - } - } - msg.peer.id = msg.msg.(MessageID).ID - s.EventChan <- EventConnect{ - Peer: msg.peer, - ID: msg.peer.id, - } - case MessageClose: - s.EventChan <- EventDisconnect{ - Peer: msg.peer, - ID: msg.peer.id, - } - default: - s.EventChan <- EventMessage{ - Peer: msg.peer, - Message: msg.msg, - } - } - case packet := <-s.rawChan: - //fmt.Println("got raw packet", packet) - // If the packet is from the matchmaker, handle it. - if s.matchmakerAddr != nil && packet.addr.String() == s.matchmakerAddr.String() { - fmt.Println("handling as matchmaker") - // TODO: Handle matchmaker packets. - return - } - // If we've never received from this address before, add it to our list of peers. - var peer *Peer - for _, p := range s.peers { - if p.addr.String() == packet.addr.String() { - peer = p - break - } - } - if peer == nil { - peer = NewPeer(packet.addr, s.localConn) - s.peers = append(s.peers, peer) - } - - if packet.readBytes > 0 { - peer.writeToPacketBuffer(packet.buffer[:packet.readBytes]) - } - - // Session is nil, try to set up a kcp session. - if peer.session == nil { - session, err := kcp.NewConn3(0, packet.addr, nil, NetDataShards, NetParityShards, peer) - if err != nil { - panic(err) - } - peer.session = session - go peer.loop(s.peerChan) - - peer.Send(MessageID{ID: s.id}) - } else { - // Send packet data to peer's virtual buffer. - peer.writeToPacketBuffer(packet.buffer[:packet.readBytes]) - } - } - } -} - -// ReadLoop runs the network read loop, handling new connections as necessary. -func (s *ServerClient) ReadLoop() { - for s.Running { - buffer := make([]byte, NetBufferSize) - n, addr, err := s.localConn.ReadFromUDP(buffer) - if err != nil { - if !os.IsTimeout(err) { - if !(strings.HasSuffix(err.Error(), "closed network connection")) { - fmt.Println(err) - } - } - break - } - packet := Packet{ - buffer: buffer, - addr: addr, - readBytes: n, - } - s.rawChan <- packet - } + return errNoNetwork } func (s *ServerClient) Close() { - if !s.Running { - return - } - for _, peer := range s.peers { - peer.Send(MessageClose{}) - s.EventChan <- EventDisconnect{ - Peer: peer, - ID: peer.id, - } - } - <-time.After(1 * time.Second) - s.closeChan <- struct{}{} } func (s *ServerClient) Peers() []*Peer { - return s.peers + return nil } diff -ruN a/resources/gpt.go b/resources/gpt.go --- a/resources/gpt.go +++ b/resources/gpt.go @@ -1,204 +1,32 @@ package resources import ( - "bytes" - "encoding/json" "fmt" - "io/ioutil" - "net/http" "github.com/kettek/go-multipath/v2" - "gopkg.in/yaml.v2" ) -type GPTResponse struct { - Choices []struct { - Message struct { - Content string `yaml:"content"` - } `yaml:"message"` - } `yaml:"choices"` -} - -type MessageBody struct { - Role string `json:"role"` - Content string `json:"content"` -} -type GPTRequestBody struct { - Model string `json:"model"` - Messages []MessageBody `json:"messages"` -} - -var gptKey string - +// FredyGames: the optional OpenAI "GPT locale" feature was removed. This stub keeps the type the rest of the game uses, never +// has a key and never touches the network. type GPT struct { - Key string - IsInitKey bool // If true, the key was from initialization - SystemPrompt string // The prompt to use for the system - MaxTokens int // 1-8192 - Model string // "gpt-3.5-turbo-16k", "gpt-3.5-turbo" or "gpt-4" - Style string // the style of the translation + Style string } -// Does some set up for GPT -// - uses the gptKey variable that is set during release builds -// - or finds the api key from "assets/key.txt" -// - sets default values func InitGPT(fs multipath.FS) *GPT { - var k string - k = "" - if gptKey != "" { - k = gptKey - } else { - fileName := "key.txt" - file, err := fs.ReadFile(fileName) - - // Check if an error was returned - if err == nil { - k = string(file) - } - } - - return &GPT{ - Key: k, - Model: "gpt-3.5-turbo-16k", - Style: "an old mumbling sage", - MaxTokens: 8192, - SystemPrompt: ` - You are an assistant who has been asked to translate the values of a YAML object into a different style. - You must emulate the style or personality of the user-requested style. - - You must abide by the following rules: - - Do not modify the keys - - You will receive a query with a YAML object containing the key value pairs of the original text. - - For each key you should create a new value that must be different from the original. - - A style will be requested that you should use for creating the new value. - - You will adhere your responses to the style and apply the given writing style or personality to it. - - There should be no escape characters in the new values. - - All key value pairs in the input must be present in the output. - - If the value is one word, the phrase should be one word. - - Your response must be YAML unmarshalable. - - Only respond with the new YAML object. - `, - } -} - -func (g *GPT) Fetch(method, url string, requestBody *[]byte) ([]byte, error) { - if method == "POST" && requestBody == nil { - return nil, fmt.Errorf("request body cannot be nil for POST requests") - } - var req *http.Request - var err error - if method == "POST" { - req, err = http.NewRequest(method, url, bytes.NewBuffer(*requestBody)) - } else { - req, err = http.NewRequest(method, url, nil) - } - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+g.Key) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode > 299 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("error: %s", string(body)) - } - body, err := ioutil.ReadAll(resp.Body) - return body, err -} - -// Creates the prompt -func (gpt *GPT) createPrompt(inputLocale *Locale, locale string) string { - prompt := fmt.Sprintf(`You must strictly apply the style/personality of "%s" to the values.`, gpt.Style) - if locale != "en" { - prompt += fmt.Sprintf(`You must translate the values into the "%s" language using the appropriate character set.`, locale) - } - prompt += "The YAML object to be modified. All of the keys in this object must be present in the output:\n" - for k, v := range *inputLocale { - prompt += fmt.Sprintf(`"%s": "%s"\n`, k, v) - } - return prompt -} - -func (gpt *GPT) GetResponse(inputLocale *Locale, locale string) (Locale, error) { - - prompt := GPTRequestBody{ - Model: gpt.Model, - Messages: []MessageBody{ - { - Role: "system", - Content: gpt.SystemPrompt, - }, - { - Role: "user", - Content: gpt.createPrompt(inputLocale, locale), - }, - }, - } - - promptJson, err := json.Marshal(prompt) - if err != nil { - return nil, err - } - - body, err := gpt.Fetch("POST", "https://api.openai.com/v1/chat/completions", &promptJson) - if err != nil { - return nil, err - } - - var s *GPTResponse - if err := json.Unmarshal(body, &s); err != nil { - return nil, err - } - if len(s.Choices) == 0 { - return nil, fmt.Errorf("no choices returned") - } - content := s.Choices[0].Message.Content - - var respLocale *Locale - if err := yaml.Unmarshal([]byte(content), &respLocale); err != nil { - return nil, err - } - return *respLocale, nil + return &GPT{Style: "an old mumbling sage"} } func (g *GPT) GetLocale(baseLocale *Locale, locale string) (*Locale, error) { - gptLocale, err := g.GetResponse(baseLocale, locale) - if err != nil { - return nil, err - } - return &gptLocale, nil + return nil, fmt.Errorf("GPT locales are not available") } func (g *GPT) CheckKey() bool { - if g.Key == "" { - return false - } - - _, err := g.Fetch("GET", "https://api.openai.com/v1/models", nil) - if err != nil { - return false - } - return true + return false } func (g *GPT) GetKey() string { - if g.IsInitKey { - return "" - } - return g.Key + return "" } func (g *GPT) SetKey(key string) { - g.Key = key - g.IsInitKey = false } diff -ruN a/states/menu/credits.go b/states/menu/credits.go --- a/states/menu/credits.go +++ b/states/menu/credits.go @@ -63,11 +63,6 @@ Text: "kettek", X: x, Y: y + 20, - Underline: true, - Callback: func() bool { - OpenURL("https://kettek.net") - return true - }, }, Hat: resources.NewSprite(ctx.R.GetAs("images", "hat-coffee", (*ebiten.Image)(nil)).(*ebiten.Image)), } @@ -92,11 +87,6 @@ Text: "liqMix", X: x, Y: y + 20, - Underline: true, - Callback: func() bool { - OpenURL("https://liq.mx/") - return true - }, }, Hat: resources.NewSprite(ctx.R.GetAs("images", "hat-pep", (*ebiten.Image)(nil)).(*ebiten.Image)), } @@ -117,11 +107,6 @@ Text: "Amaruuk", X: x, Y: y + 20, - Underline: true, - Callback: func() bool { - OpenURL("https://birdtooth.studio/") - return true - }, }, Hat: resources.NewSprite(ctx.R.GetAs("images", "hat-egg", (*ebiten.Image)(nil)).(*ebiten.Image)), } @@ -155,11 +140,6 @@ } if inpututil.IsMouseButtonJustReleased(ebiten.MouseButton0) { - if c.jam.Hit(float64(x), float64(y)) { - OpenURL("https://itch.io/jam/ebitengine-game-jam-2023") - return nil - } - for _, b := range c.items { if b.Hovered() { if b.Activate() { @@ -188,7 +168,7 @@ func (c *Credits) Draw(ctx states.DrawContext) { c.logo.Draw(ctx) - c.jam.Draw(ctx) + // FredyGames: the game-jam logo of the credits screen is not drawn. for _, b := range c.items { b.Draw(ctx) } diff -ruN a/states/menu/gpt.go b/states/menu/gpt.go --- a/states/menu/gpt.go +++ b/states/menu/gpt.go @@ -1,215 +0,0 @@ -package menu - -import ( - "image/color" - "strings" - - "github.com/ketMix/retromancer/resources" - - "github.com/ketMix/retromancer/states" - - "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/inpututil" -) - -type GPTOptions struct { - back, checkKey, generate *resources.ButtonItem - key, locale, style *resources.InputItem - buttons []*resources.ButtonItem - inputs []*resources.InputItem - click *resources.Sound - gptKeyIsValid bool - gptIsActive bool -} - -func (s *GPTOptions) Init(ctx states.Context) error { - s.gptKeyIsValid = ctx.L.CheckGPTKey() - s.click = ctx.R.GetAs("sounds", "click", (*resources.Sound)(nil)).(*resources.Sound) - - x, y := 1280, 720 - centerX := float64(x / 4) - width := float64(350) - s.key = &resources.InputItem{ - X: centerX, - Y: float64(y/20) * 6, - Width: width, - Text: ctx.L.GetGPTKey(), - Placeholder: ctx.L.Get("OpenAI API Key"), - Callback: func() bool { - return false - }, - } - s.style = &resources.InputItem{ - X: centerX, - Y: float64(y/20) * 7, - Width: width, - Placeholder: ctx.L.Get("Writing Style"), - Text: ctx.L.GetGPTStyle(), - Callback: func() bool { - return false - }, - } - s.locale = &resources.InputItem{ - X: centerX, - Y: float64(y/20) * 8, - Width: 100, - Text: ctx.L.Locale(), - Placeholder: ctx.L.Get("Language"), - Callback: func() bool { - return false - }, - } - - s.checkKey = &resources.ButtonItem{ - X: centerX - width/4, - Y: 335, - Text: ctx.L.Get("Check Key"), - Callback: func() bool { - s.click.Play(1.0) - if s.key.Text != "" { - ctx.L.SetGPTKey(s.key.Text) - } - if ctx.L.CheckGPTKey() { - s.gptKeyIsValid = true - } else { - s.gptKeyIsValid = false - } - return true - }, - } - - s.generate = &resources.ButtonItem{ - X: centerX + width/4, - Y: 335, - Text: ctx.L.Get("Generate"), - Callback: func() bool { - s.click.Play(1.0) - ctx.L.SetGPTKey(s.key.Text) - if ctx.L.CheckGPTKey() { - s.gptKeyIsValid = true - ctx.L.SetGPTStyle(s.style.Text) - ctx.L.SetLocale(s.locale.Text, true) - } else { - s.gptKeyIsValid = false - } - return true - }, - } - s.back = &resources.ButtonItem{ - Text: ctx.L.Get("Back"), - X: 30, - Y: 335, - Callback: func() bool { - s.click.Play(1.0) - ctx.StateMachine.PopState(nil) - return false - }, - } - s.inputs = append(s.inputs, s.key, s.style, s.locale) - s.buttons = append(s.buttons, s.checkKey, s.generate, s.back) - return nil -} - -func (s *GPTOptions) Finalize(ctx states.Context) error { - return nil -} - -func (s *GPTOptions) Enter(ctx states.Context, v interface{}) error { - return nil -} - -func (s *GPTOptions) Update(ctx states.Context) error { - s.gptIsActive = ctx.L.GPTIsActive() - s.generate.Text = ctx.L.Get("Generate") - s.back.Text = ctx.L.Get("Back") - s.locale.Placeholder = ctx.L.Get("Language") - s.style.Placeholder = ctx.L.Get("Writing Style") - - x, y := ebiten.CursorPosition() - for _, m := range s.buttons { - m.CheckState(float64(x), float64(y)) - } - if inpututil.IsMouseButtonJustReleased(ebiten.MouseButton0) { - for _, m := range s.buttons { - if m.Hovered() { - if m.Activate() { - return nil - } - } - } - } - for _, m := range s.inputs { - m.CheckState(float64(x), float64(y)) - m.Update() - } - if inpututil.IsMouseButtonJustReleased(ebiten.MouseButton0) { - for _, m := range s.inputs { - if m.Hovered() { - if m.Activate() { - return nil - } - } else { - m.Deactivate() - } - } - } - return nil -} - -func (s *GPTOptions) Draw(ctx states.DrawContext) { - for _, m := range s.buttons { - m.Draw(ctx) - } - for _, m := range s.inputs { - m.Draw(ctx) - } - x, y := 1280, 720 - - if !s.gptKeyIsValid { - ctx.Text.SetScale(1.0) - ctx.Text.SetColor(color.RGBA{255, 0, 0, 255}) - ctx.Text.Draw(ctx.Screen, ctx.L.Get("GPTKeyNotValid"), x/4, (y / 24)) - } else { - ctx.Text.SetScale(1.0) - ctx.Text.SetColor(color.RGBA{0, 255, 0, 255}) - ctx.Text.Draw(ctx.Screen, ctx.L.Get("GPTKeyValid"), x/4, (y / 24)) - } - - if !s.gptIsActive { - ctx.Text.SetScale(1.0) - ctx.Text.SetColor(color.RGBA{255, 0, 0, 255}) - ctx.Text.Draw(ctx.Screen, ctx.L.Get("GPTNotActive"), x/4, (y/24)*2) - } - - // Draw instruction - text := strings.Split(ctx.L.Get("GPTInstructions"), "\n") - splitText := make([]string, 0) - maxLen := 60 - for _, line := range text { - if len(line) <= maxLen { - splitText = append(splitText, line) - continue - } - // split text into lines that are less than maxLen - // split on spaces - words := strings.Split(line, " ") - currentLine := "" - for _, word := range words { - if len(currentLine)+len(word) > maxLen { - splitText = append(splitText, currentLine) - currentLine = "" - } - currentLine += word + " " - } - splitText = append(splitText, currentLine) - } - y = (y/20)*4 - (len(splitText)/2)*int(ctx.Text.Utils().GetLineHeight()) - for _, line := range splitText { - { - ctx.Text.SetScale(1.0) - ctx.Text.SetColor(color.White) - ctx.Text.Draw(ctx.Screen, line, x/4, y) - } - y += int(ctx.Text.Utils().GetLineHeight()) - } -} diff -ruN a/states/menu/lobby.go b/states/menu/lobby.go --- a/states/menu/lobby.go +++ b/states/menu/lobby.go @@ -61,9 +61,6 @@ for _, item := range s.items { if item == s.multiplayerItem { s.multiplayerItem.SetHidden(true) - s.lobbyItem.SetHidden(false) - s.joinItem.SetHidden(false) - s.hostItem.SetHidden(false) s.playerEntries = append(s.playerEntries, &PlayerEntry{}) s.playerEntries[len(s.playerEntries)-1].Init(s, ctx) break diff -ruN a/states/menu/menu.go b/states/menu/menu.go --- a/states/menu/menu.go +++ b/states/menu/menu.go @@ -15,7 +15,7 @@ bg1, bg2 *resources.Sprite bg1logo, bg2logo *resources.Sprite - play, credits, gpt *resources.TextItem + play, credits *resources.TextItem sprites resources.Sprites buttons []*resources.TextItem click *resources.Sound @@ -60,18 +60,8 @@ }, } - m.gpt = &resources.TextItem{ - Text: "GPT Options", - X: x, - Y: y, - Callback: func() bool { - m.click.Play(1.0) - ctx.StateMachine.PushState(&GPTOptions{}) - return true - }, - } m.sprites = append(m.sprites, m.bg1, m.bg2) - m.buttons = append(m.buttons, m.play, m.gpt, m.credits) + m.buttons = append(m.buttons, m.play, m.credits) m.click = ctx.R.GetAs("sounds", "click", (*resources.Sound)(nil)).(*resources.Sound) diff -ruN a/states/menu/open_js.go b/states/menu/open_js.go --- a/states/menu/open_js.go +++ b/states/menu/open_js.go @@ -2,11 +2,7 @@ package menu -import ( - "syscall/js" -) - +// FredyGames: the web build never opens other pages. func OpenURL(path string) error { - js.Global().Get("window").Call("open", path, "_blank") return nil }