boxbrawl/system/ui.go

266 lines
6.7 KiB
Go

package system
import (
"fmt"
"image/color"
"math"
"github.com/assemblaj/ggpo"
"code.rocketnine.space/tslocum/boxbrawl/component"
"code.rocketnine.space/tslocum/boxbrawl/world"
"code.rocketnine.space/tslocum/etk"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
const uiStartPrompt = `Box Brawl
`
const uiComputerPrompt = `Press <Enter> to play against the computer.`
const uiHostPrompt = `Press <H> to host a match against a remote player.`
const uiHostInfoPrompt = `Type a port to host a match against a remote player.`
const uiHostStartPrompt = `Press <Enter> to start hosting.`
const uiRemotePrompt = `Type an IP address and port (address:port) to play against a remote player.`
const uiConnectPrompt = `Press <Enter> to connect.`
const uiBrowserPrompt = `Playing against remote players is unavailable in the browser version.
Download Box Brawl for Windows or Linux to play against remote players.`
const uiHostListeningPrompt = `Waiting for a connection on %s...`
const uiClientConnectingPrompt = `Connecting to %s...`
type UISystem struct {
*component.Once
initialized bool
buffer *etk.Text
updateTicks int
hitboxImg *ebiten.Image
}
func (u *UISystem) initialize() {
u.buffer = etk.NewText("")
u.updateBuffer()
inputDemo := etk.NewFlex()
inputDemo.SetVertical(true)
inputDemo.AddChild(u.buffer)
etk.SetRoot(inputDemo)
etk.Layout(world.InternalScreenWidth, world.InternalScreenHeight)
u.hitboxImg = ebiten.NewImage(32, 32)
u.initialized = true
}
func (u *UISystem) updateBuffer() {
prompt := []byte(uiStartPrompt)
if world.WASM {
prompt = append(prompt, []byte("\n\n"+uiComputerPrompt)...)
prompt = append(prompt, []byte("\n\n"+uiBrowserPrompt)...)
} else if world.ConnectionActive {
if world.ConnectPromptHost {
prompt = append(prompt, []byte("\n\n"+fmt.Sprintf(uiHostListeningPrompt, world.ConnectPromptText))...)
} else {
prompt = append(prompt, []byte("\n\n"+fmt.Sprintf(uiClientConnectingPrompt, world.ConnectPromptText))...)
}
} else {
promptEntered := len(world.ConnectPromptText) != 0
if promptEntered || world.ConnectPromptHost {
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)...)
} else {
prompt = append(prompt, []byte("\n\n"+uiConnectPrompt)...)
prompt = append(prompt, []byte("\n\n"+uiRemotePrompt)...)
}
} else {
prompt = append(prompt, []byte("\n\n"+uiComputerPrompt)...)
prompt = append(prompt, []byte("\n\n"+uiHostPrompt)...)
prompt = append(prompt, []byte("\n\n"+uiRemotePrompt)...)
}
}
u.buffer.Clear()
u.buffer.Write(prompt)
}
func (u *UISystem) Update(e gohan.Entity) error {
if !u.initialized {
u.initialize()
}
if !world.ConnectPromptVisible {
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
if inpututil.IsKeyJustPressed(ebiten.KeyDigit0) {
a += "0"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit1) {
a += "1"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit2) {
a += "2"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit3) {
a += "3"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit4) {
a += "4"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit5) {
a += "5"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit6) {
a += "6"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit7) {
a += "7"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit8) {
a += "8"
}
if inpututil.IsKeyJustPressed(ebiten.KeyDigit9) {
a += "9"
}
if inpututil.IsKeyJustPressed(ebiten.KeyPeriod) {
a += "."
}
if inpututil.IsKeyJustPressed(ebiten.KeySemicolon) && ebiten.IsKeyPressed(ebiten.KeyShift) {
a += ":"
}
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
}
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeyKPEnter) {
world.ConnectPromptConfirmed = true
}
if !updated {
u.updateTicks++
if u.updateTicks == 6 {
u.updateBuffer()
u.updateTicks = 0
}
}
return etk.Update()
}
func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
if !u.initialized {
u.initialize()
}
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)
}
}
}
}
if world.Debug != 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
}