From b340d821f700e332bffa1b7d271182ae1b701ded Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Mon, 9 Jan 2023 23:10:33 -0800 Subject: [PATCH] Add frame data system --- component/player.go | 82 +++++++++++++++++-- flags.go | 1 + game/game.go | 187 ++++++++++++++++++++++++-------------------- game/session.go | 14 +++- go.mod | 6 +- go.sum | 12 +-- main.go | 3 +- system/player.go | 7 ++ system/ui.go | 106 ++++++++++++++++++++++--- world/world.go | 17 ++-- 10 files changed, 321 insertions(+), 114 deletions(-) diff --git a/component/player.go b/component/player.go index 0afccfe..eb95bbe 100644 --- a/component/player.go +++ b/component/player.go @@ -2,6 +2,7 @@ package component import ( "fmt" + "image" "image/color" ) @@ -12,6 +13,74 @@ const ( ActionPunch ) +type HitboxType int + +const ( + HitboxInvalid HitboxType = iota + HitboxNormal + HitboxHurt +) + +type FrameData struct { + T HitboxType + R image.Rectangle +} + +const playerSize = 20 + +// AllPlayerFrames defines all frame data for the game. Frames are defined in reverse order. +var AllPlayerFrames = [][][]FrameData{ + { // ActionIdle + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + }, { // ActionPunch + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + { + T: HitboxHurt, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + { + { + T: HitboxNormal, + R: image.Rect(0, 0, playerSize, playerSize), + }, + }, + }, +} + type Player struct { X float64 Y float64 @@ -23,14 +92,17 @@ type Player struct { } func (p *Player) String() string { - return fmt.Sprintf("Player %d: X:%f Y:%f Color: %s", p.PlayerNum, p.X, p.Y, p.Color) + return fmt.Sprintf("Player %d: X:%f Y:%f Action: %d", p.PlayerNum, p.X, p.Y, p.Action) } func (p *Player) Clone() Player { result := Player{} - result.X = p.X - result.Y = p.Y - result.Color = p.Color - result.PlayerNum = p.PlayerNum + result = *p return result } + +func OffsetRect(r image.Rectangle, x int, y int) image.Rectangle { + r.Min.X, r.Min.Y = r.Min.X+x, r.Min.Y+y + r.Max.X, r.Max.Y = r.Max.X+x, r.Max.Y+y + return r +} diff --git a/flags.go b/flags.go index 0248f85..9d553ab 100644 --- a/flags.go +++ b/flags.go @@ -25,6 +25,7 @@ func parseFlags() { flag.StringVar(&connectAddress, "connect", "", "connect to a match at specified address:port") flag.IntVar(&world.LocalPort, "local", 0, "set local port (this is not normally required)") flag.BoolVar(&printDebug, "debug", false, "enable printing debug messages") + flag.IntVar(&world.TPS, "tps", world.DefaultTPS, "set ticks per second (this is not normally required)") flag.Parse() if fullscreen { diff --git a/game/game.go b/game/game.go index dfbe896..a4e2eb8 100644 --- a/game/game.go +++ b/game/game.go @@ -19,10 +19,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/inpututil" ) -var backend ggpo.Backend - -var currentPlayer = 1 - type Game struct { Players []component.Player } @@ -30,18 +26,23 @@ type Game struct { var addedGame bool func NewGame() (*Game, error) { - var player1 = component.Player{ - X: 50, - Y: 50, - Color: color.RGBA{255, 0, 0, 255}, - PlayerNum: 1, + player1 := component.Player{ + PlayerNum: 1, + Color: color.RGBA{255, 0, 0, 255}, + Action: component.ActionIdle, + ActionTicksLeft: 1, + X: 50, + Y: 50, } - var player2 = component.Player{ - X: 150, - Y: 50, - Color: color.RGBA{0, 0, 255, 255}, - PlayerNum: 2, + player2 := component.Player{ + PlayerNum: 2, + Color: color.RGBA{0, 0, 255, 255}, + Action: component.ActionIdle, + ActionTicksLeft: 1, + X: 150, + Y: 50, } + g := &Game{ Players: []component.Player{player1, player2}, } @@ -78,68 +79,78 @@ func (g *Game) Layout(_, _ int) (screenWidth, screenHeight int) { return world.ScreenWidth, world.ScreenHeight } +func (g *Game) startLocalGame() { + log.Println("Playing against the computer") + + // TODO +} + +func (g *Game) startNetworkGame() { + address := "" + port := world.ConnectPromptText + if strings.ContainsRune(port, ':') { + split := strings.Split(port, ":") + if len(split) == 2 { + address = split[0] + port = split[1] + } + } + p, err := strconv.Atoi(port) + if err != nil { + log.Fatalf("failed to read port: %s", err) + } + + localPort := world.LocalPort + if localPort == 0 { + localPort = p + 1 + } + if world.LocalPort != 0 { + log.Printf("Client port for connection: %d", world.LocalPort) + } + + numPlayers := 2 + playerSize := 20 + + players := make([]ggpo.Player, numPlayers) + if world.ConnectPromptHost { + log.Printf("Hosting at " + address + ":" + port + "...") + + players[0] = ggpo.NewLocalPlayer(playerSize, 1) + players[1] = ggpo.NewRemotePlayer(playerSize, 2, "127.0.0.1", localPort) + } else { + log.Printf("Connecting to " + address + ":" + port + "...") + + world.CurrentPlayer = 2 + players[0] = ggpo.NewRemotePlayer(playerSize, 1, address, p) + players[1] = ggpo.NewLocalPlayer(playerSize, 2) + } + + l := p + if !world.ConnectPromptHost { + l = localPort + } + + g.InitNetworking(l, numPlayers, players, 0) + + world.ConnectionActive = true +} func (g *Game) Update() error { if ebiten.IsWindowBeingClosed() || (!world.WASM && ebiten.IsKeyPressed(ebiten.KeyEscape)) { g.Exit() return nil } - if world.ConnectPromptConfirmed && !world.ConnectPromptActive { - address := "" - port := world.ConnectPromptText - if strings.ContainsRune(port, ':') { - split := strings.Split(port, ":") - if len(split) == 2 { - address = split[0] - port = split[1] - } - } - p, err := strconv.Atoi(port) - if err != nil { - log.Fatalf("failed to read port: %s", err) - } - - log.Println("start networking") - - localPort := world.LocalPort - if localPort == 0 { - localPort = p + 1 - } - - numPlayers := 2 - playerSize := 20 - players := make([]ggpo.Player, numPlayers) - if world.ConnectPromptHost { - log.Printf("Hosting at " + address + ":" + port + "...") - - players[0] = ggpo.NewLocalPlayer(playerSize, 1) - players[1] = ggpo.NewRemotePlayer(playerSize, 2, "127.0.0.1", localPort) + if world.ConnectPromptConfirmed && !world.ConnectionActive { + if world.ConnectPromptText == "" { + g.startLocalGame() } else { - log.Printf("Connecting to " + address + ":" + port + "...") - - players[0] = ggpo.NewRemotePlayer(playerSize, 1, address, p) - players[1] = ggpo.NewLocalPlayer(playerSize, 2) - currentPlayer = 2 + g.startNetworkGame() } - - if world.LocalPort != 0 { - log.Printf("Client port for connection: %d", world.LocalPort) - } - - l := p - if !world.ConnectPromptHost { - l = localPort - } - - g.InitNetworking(l, numPlayers, players, 0) - g.playerStateUpdated() - - world.ConnectPromptActive = true } - if world.ConnectPromptActive { - err := backend.Idle(0) // TODO Why 0? + if world.ConnectionActive { + err := world.Backend.Idle(0) if err != nil { panic(err) } @@ -174,8 +185,8 @@ func (g *Game) InitNetworking(localPort int, numPlayers int, players []ggpo.Play peer := ggpo.NewPeer(session, localPort, numPlayers, inputSize) //peer := ggpo.NewSyncTest(&session, numPlayers, 8, inputSize, true) - backend = &peer - session.backend = backend + world.Backend = &peer + session.backend = world.Backend peer.InitializeConnection() peer.Start() @@ -186,13 +197,13 @@ func (g *Game) InitNetworking(localPort int, numPlayers int, players []ggpo.Play var handle ggpo.PlayerHandle result = peer.AddPlayer(&players[i], &handle) if players[i].PlayerType == ggpo.PlayerTypeLocal { - currentPlayer = int(handle) + world.CurrentPlayer = int(handle) } if result != nil { log.Fatalf("There's an issue from AddPlayer") } if players[i].PlayerType == ggpo.PlayerTypeLocal { - peer.SetFrameDelay(handle, FRAME_DELAY) + peer.SetFrameDelay(handle, frameDelay) } } peer.SetDisconnectTimeout(3000) @@ -204,7 +215,7 @@ func (g *Game) RunFrame() { buffer := encodeInputs(input) //fmt.Println("Attempting to add local inputs") - result := backend.AddLocalInput(ggpo.PlayerHandle(currentPlayer), buffer, len(buffer)) + result := world.Backend.AddLocalInput(ggpo.PlayerHandle(world.CurrentPlayer), buffer, len(buffer)) //fmt.Println("Attempt to add local inputs complete") if result == nil { @@ -213,7 +224,7 @@ func (g *Game) RunFrame() { disconnectFlags := 0 //fmt.Println("Attempting to synchronize inputs") - values, result = backend.SyncInput(&disconnectFlags) + values, result = world.Backend.SyncInput(&disconnectFlags) if result == nil { //fmt.Println("Attempt synchronize inputs was sucessful") inputs := decodeInputs(values) @@ -225,25 +236,17 @@ func (g *Game) RunFrame() { } else { //fmt.Printf("Attempt to add local inputs unsuccessful: %s\n", result) } + + g.playerStateUpdated() } func (g *Game) AdvanceFrame(inputs []InputBits, disconnectFlags int) { g.UpdateByInputs(inputs) - err := backend.AdvanceFrame(uint32(g.Checksum())) + + err := world.Backend.AdvanceFrame(uint32(g.Checksum())) if err != nil { panic(err) } - - for i := range g.Players { - if g.Players[i].ActionTicksLeft != 0 { - g.Players[i].ActionTicksLeft-- - if g.Players[i].ActionTicksLeft == 0 { - g.Players[i].Action = component.ActionIdle - } - } - } - - g.playerStateUpdated() } func (g *Game) UpdateByInputs(inputs []InputBits) { @@ -264,7 +267,23 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { if g.Players[i].Action == component.ActionIdle { if input.isButtonOn(ButtonPunch) { g.Players[i].Action = component.ActionPunch - g.Players[i].ActionTicksLeft = 25 // TODO + g.Players[i].ActionTicksLeft = len(component.AllPlayerFrames[component.ActionPunch]) // TODO + continue + } + } + + if g.Players[i].ActionTicksLeft != 0 { + g.Players[i].ActionTicksLeft-- + if g.Players[i].ActionTicksLeft == 0 { + g.Players[i].Action = component.ActionIdle + g.Players[i].ActionTicksLeft = len(component.AllPlayerFrames[component.ActionIdle]) // TODO + } + + // TODO Apply hitboxes + if g.Players[i].ActionTicksLeft != 0 { + //frameNum := g.Players[i].ActionTicksLeft - 1 + //frame := component.AllPlayerFrames[g.Players[i].Action][frameNum] + //log.Printf("frame %+v", frame) } } } diff --git a/game/session.go b/game/session.go index dc18c37..7af26d1 100644 --- a/game/session.go +++ b/game/session.go @@ -12,7 +12,7 @@ import ( "github.com/assemblaj/ggpo" ) -const FRAME_DELAY int = 2 +const frameDelay int = 2 type GameSession struct { backend ggpo.Backend @@ -92,5 +92,17 @@ func (g *GameSession) OnEvent(info *ggpo.Event) { log.Println("Connection resumed") case ggpo.EventCodeDisconnectedFromPeer: log.Println("Connection lost") + default: + if world.Debug == 0 { + return + } + switch info.Code { + case ggpo.EventCodeConnectedToPeer: + log.Println("Connected to peer") + case ggpo.EventCodeSynchronizingWithPeer: + log.Println("Synchronizing with peer") + case ggpo.EventCodeSynchronizedWithPeer: + log.Println("Synchronized with peer") + } } } diff --git a/go.mod b/go.mod index dec7a07..ba4952d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( code.rocketnine.space/tslocum/etk v0.0.0-20230103193701-368514415e01 code.rocketnine.space/tslocum/gohan v1.0.0 - github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e + github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51 github.com/hajimehoshi/ebiten/v2 v2.4.15 ) @@ -15,8 +15,8 @@ require ( github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/hajimehoshi/file2byteslice v1.0.0 // indirect github.com/jezek/xgb v1.1.0 // indirect - golang.org/x/exp v0.0.0-20230105000112-eab7a2c85304 // indirect - golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304 // indirect + golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a // indirect + golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a // indirect golang.org/x/image v0.3.0 // indirect golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect golang.org/x/sys v0.4.0 // indirect diff --git a/go.sum b/go.sum index 8830a5a..6ce5d01 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ code.rocketnine.space/tslocum/gohan v1.0.0/go.mod h1:12yOt5Ygl/RVwnnZSVZRuS1W6gC code.rocketnine.space/tslocum/messeji v1.0.2 h1:3/68FnXWaBDMhfUGb8FvNpVgAHY8DX+VL7pyA/CcY94= code.rocketnine.space/tslocum/messeji v1.0.2/go.mod h1:bSXsyjvKhFXQ7GsUxWZdO2JX83xOT/VTqFCR04thk+c= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e h1:d/uJpkRgHj5EJ06CX0TFFQLfvAeHlfWY+xfLUBkVeus= -github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e/go.mod h1:ZKiAYEZgxDlGHGeP/VZsv1+xIRo9kQpgUFmjP/PR0lQ= +github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51 h1:7utlNj3OWPUxtFgav/0Eu1EXOrsNS5lrq+NWnwTRnAs= +github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51/go.mod h1:ZKiAYEZgxDlGHGeP/VZsv1+xIRo9kQpgUFmjP/PR0lQ= github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= github.com/ebitengine/purego v0.1.1 h1:HI8nW+LniW9Yb34k34jBs8nz+PNzsw68o7JF8jWFHHE= github.com/ebitengine/purego v0.1.1/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= @@ -38,10 +38,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp v0.0.0-20230105000112-eab7a2c85304 h1:YUqj+XKtfrn3kXjFIiZ8jwKROD7ioAOOHUuo3ZZ2opc= -golang.org/x/exp v0.0.0-20230105000112-eab7a2c85304/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304 h1:ezMmyIKsGPwRz+IHa53wCpw87I2TremhqQ8o79ytDEk= -golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= +golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU= +golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a h1:mTWP1/jZnkR5zmffmGg9HfL6w81dS/u6ZJSOa4i8ot8= +golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= diff --git a/main.go b/main.go index 8b22545..03193c4 100644 --- a/main.go +++ b/main.go @@ -17,11 +17,12 @@ func main() { ebiten.SetWindowSize(world.DefaultScreenWidth, world.DefaultScreenHeight) ebiten.SetWindowClosingHandled(true) ebiten.SetFPSMode(ebiten.FPSModeVsyncOn) - ebiten.SetTPS(world.TPS) ebiten.SetRunnableOnUnfocused(true) parseFlags() + ebiten.SetTPS(world.TPS) + g, err := game.NewGame() if err != nil { log.Fatal(err) diff --git a/system/player.go b/system/player.go index 2e66496..ae88038 100644 --- a/system/player.go +++ b/system/player.go @@ -49,6 +49,13 @@ func (s *PlayerSystem) Draw(e gohan.Entity, screen *ebiten.Image) error { r := image.Rect(int(p.X), int(p.Y), int(p.X)+size, int(p.Y)+size) screen.SubImage(r).(*ebiten.Image).Fill(p.Color) + // TODO animate + /*if p.ActionTicksLeft != 0 { + frameNum := p.ActionTicksLeft - 1 + frame := component.AllPlayerFrames[p.Action][frameNum] + log.Printf("frame %+v", frame) + }*/ + switch p.Action { case component.ActionPunch: ebitenutil.DebugPrintAt(screen, "PUNCH", int(p.X), int(p.Y)) diff --git a/system/ui.go b/system/ui.go index 4b3e6da..11cd786 100644 --- a/system/ui.go +++ b/system/ui.go @@ -2,6 +2,10 @@ package system import ( "fmt" + "image/color" + "math" + + "github.com/assemblaj/ggpo" "code.rocketnine.space/tslocum/boxbrawl/component" "code.rocketnine.space/tslocum/boxbrawl/world" @@ -31,6 +35,9 @@ type UISystem struct { initialized bool buffer *etk.Text + updateTicks int + + hitboxImg *ebiten.Image } func (u *UISystem) initialize() { @@ -45,6 +52,8 @@ func (u *UISystem) initialize() { etk.SetRoot(inputDemo) etk.Layout(world.InternalScreenWidth, world.InternalScreenHeight) + u.hitboxImg = ebiten.NewImage(32, 32) + u.initialized = true } @@ -54,7 +63,7 @@ func (u *UISystem) updateBuffer() { if world.WASM { prompt = append(prompt, []byte("\n\n"+uiComputerPrompt)...) prompt = append(prompt, []byte("\n\n"+uiBrowserPrompt)...) - } else if world.ConnectPromptActive { + } else if world.ConnectionActive { if world.ConnectPromptHost { prompt = append(prompt, []byte("\n\n"+fmt.Sprintf(uiHostListeningPrompt, world.ConnectPromptText))...) } else { @@ -63,7 +72,7 @@ func (u *UISystem) updateBuffer() { } else { promptEntered := len(world.ConnectPromptText) != 0 if promptEntered || world.ConnectPromptHost { - prompt = append(prompt, []byte("\n\n"+world.ConnectPromptText)...) + prompt = append(prompt, []byte("\n\n"+world.ConnectPromptText+"_")...) if world.ConnectPromptHost { prompt = append(prompt, []byte("\n\n"+uiHostInfoPrompt)...) prompt = append(prompt, []byte("\n\n"+uiHostStartPrompt)...) @@ -91,10 +100,13 @@ func (u *UISystem) Update(e gohan.Entity) error { return nil } + var updated bool + if !world.WASM { if len(world.ConnectPromptText) == 0 && ebiten.IsKeyPressed(ebiten.KeyH) { world.ConnectPromptHost = true u.updateBuffer() + updated = true } var a string @@ -137,14 +149,17 @@ func (u *UISystem) Update(e gohan.Entity) error { if a != "" { world.ConnectPromptText += a u.updateBuffer() + updated = true } if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) { if len(world.ConnectPromptText) != 0 { world.ConnectPromptText = world.ConnectPromptText[:len(world.ConnectPromptText)-1] u.updateBuffer() + updated = true } else if world.ConnectPromptHost { world.ConnectPromptHost = false u.updateBuffer() + updated = true } } } @@ -153,6 +168,14 @@ func (u *UISystem) Update(e gohan.Entity) error { world.ConnectPromptConfirmed = true } + if !updated { + u.updateTicks++ + if u.updateTicks == 6 { + u.updateBuffer() + u.updateTicks = 0 + } + } + return etk.Update() } @@ -161,17 +184,82 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error { u.initialize() } - if !world.ConnectPromptVisible { - return nil - } + if world.ConnectPromptVisible { + err := etk.Draw(screen) + if err != nil { + return err + } + } else if world.Debug != 0 { // In-game and debug mode is enabled + var p *component.Player + for i := 0; i < 2; i++ { + if i == 0 { + p = &world.Player1 + } else { + p = &world.Player2 + } + if p.ActionTicksLeft != 0 { + frameNum := p.ActionTicksLeft - 1 + allData := component.AllPlayerFrames[p.Action][frameNum] + + for _, data := range allData { + fillColor := color.RGBA{0, 255, 0, 255} + switch data.T { + case component.HitboxHurt: + fillColor = color.RGBA{0, 0, 255, 255} + } + + bounds := u.hitboxImg.Bounds() + if bounds.Dx() != data.R.Dx() || bounds.Dy() != data.R.Dy() { + u.hitboxImg = ebiten.NewImage(data.R.Dx(), data.R.Dy()) + } + u.hitboxImg.Clear() + u.hitboxImg.Fill(fillColor) + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(p.X, p.Y) + op.ColorM.Scale(1, 1, 1, 1) + screen.DrawImage(u.hitboxImg, op) + } + } + } - err := etk.Draw(screen) - if err != nil { - return err } if world.Debug != 0 { - ebitenutil.DebugPrintAt(screen, fmt.Sprintf("ENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", gohan.CurrentEntities(), gohan.CurrentUpdates(), gohan.CurrentDraws(), ebiten.ActualTPS(), ebiten.ActualFPS()), 2, 0) + var ping int64 + var framesBehind float64 + if !world.ConnectPromptVisible { + p1Stats, err := world.Backend.GetNetworkStats(ggpo.PlayerHandle(1)) + if err != nil { + return fmt.Errorf("failed to get network stats for player 1: %s", err) + } + p2Stats, err := world.Backend.GetNetworkStats(ggpo.PlayerHandle(2)) + if err != nil { + return fmt.Errorf("failed to get network stats for player 2: %s", err) + } + + yourStats := p1Stats + if world.CurrentPlayer == 1 { + yourStats = p2Stats + } + + ping = yourStats.Network.Ping + framesBehind = math.Round(float64(yourStats.Timesync.LocalFramesBehind)) + } + + framesLabel := "AHEAD" + if framesBehind > 0 { + framesLabel = "BEHIND" + } + + ebitenutil.DebugPrintAt(screen, + fmt.Sprintf("FRAMES %s %.0f\nPING %d\nTPS %0.0f\nFPS %0.0f", + framesLabel, + math.Abs(framesBehind), + ping, + ebiten.ActualTPS(), + ebiten.ActualFPS()), + 2, 0) } return nil } diff --git a/world/world.go b/world/world.go index 679ea49..39bc076 100644 --- a/world/world.go +++ b/world/world.go @@ -2,11 +2,12 @@ package world import ( "code.rocketnine.space/tslocum/boxbrawl/component" + "github.com/assemblaj/ggpo" ) -const TPS = 60 - const ( + DefaultTPS = 60 + DefaultScreenWidth = 1280 DefaultScreenHeight = 720 @@ -14,20 +15,26 @@ const ( ) var ( + TPS = DefaultTPS + ScreenWidth, ScreenHeight = 0, 0 LocalPort int - ConnectPromptVisible = true + ConnectPromptVisible = true // When false, we are connected ConnectPromptText string ConnectPromptHost bool ConnectPromptConfirmed bool - ConnectPromptActive bool + ConnectionActive bool - Debug = 1 + Debug = 1 // TODO WASM bool Player1 component.Player Player2 component.Player + + CurrentPlayer = 1 + + Backend ggpo.Backend )