RegisterMultiplayerHost exposes the multiplayer arena netcode server.
{
initial := multiplayerState{Players: make(map[string]multiplayerPlayer)}
srv := netcode.NewServer(multiplayerChannel, initial, applyMultiplayerCommand)
host.Register(srv.HostComponent())
ticker := time.NewTicker(multiplayerTick)
go func() {
defer ticker.Stop()
var tick int64
for range ticker.C {
tick += int64(multiplayerTick / time.Millisecond)
srv.Update(func(state *multiplayerState) {
stepMultiplayer(state, multiplayerTick.Seconds())
})
srv.Broadcast(tick)
}
}()
}
{
payload, ok := cmd.(map[string]any)
if !ok {
return
}
session, _ := payload["session"].(string)
if session == "" {
return
}
if state.Players == nil {
state.Players = make(map[string]multiplayerPlayer)
}
action, _ := payload["type"].(string)
switch action {
case "join":
ensurePlayer(state, session)
state.Winner = ""
return
}
player := ensurePlayer(state, session)
if !player.Alive {
state.Players[session] = player
return
}
dx := floatFrom(payload["dx"])
dy := floatFrom(payload["dy"])
mag := math.Hypot(dx, dy)
if mag > 1 {
dx /= mag
dy /= mag
mag = 1
}
player.VX = dx * playerSpeed
player.VY = dy * playerSpeed
aimX := floatFrom(payload["aimX"])
aimY := floatFrom(payload["aimY"])
if aimLen := math.Hypot(aimX, aimY); aimLen > 0 {
player.AimX = aimX / aimLen
player.AimY = aimY / aimLen
} else if mag > 0 {
norm := math.Hypot(dx, dy)
if norm > 0 {
player.AimX = dx / norm
player.AimY = dy / norm
}
}
shoot := boolFrom(payload["shoot"])
if shoot && player.Cooldown <= 0 && player.Alive {
ax, ay := player.AimX, player.AimY
if math.Hypot(ax, ay) == 0 {
ax, ay = 0, -1
player.AimX, player.AimY = ax, ay
}
bullet := multiplayerBullet{
X: player.X + ax*(playerRadius+bulletRadius),
Y: player.Y + ay*(playerRadius+bulletRadius),
VX: ax * bulletSpeed,
VY: ay * bulletSpeed,
Owner: session,
}
state.Bullets = append(state.Bullets, bullet)
player.Cooldown = shootCooldownSeconds
}
state.Players[session] = player
}
{
if state.Players == nil {
state.Players = make(map[string]multiplayerPlayer)
}
for id, player := range state.Players {
if player.Cooldown > 0 {
player.Cooldown -= dt
if player.Cooldown < 0 {
player.Cooldown = 0
}
}
if player.Alive {
player.X += player.VX * dt
player.Y += player.VY * dt
if player.X < playerRadius {
player.X = playerRadius
}
if player.X > arenaWidth-playerRadius {
player.X = arenaWidth - playerRadius
}
if player.Y < playerRadius {
player.Y = playerRadius
}
if player.Y > arenaHeight-playerRadius {
player.Y = arenaHeight - playerRadius
}
}
state.Players[id] = player
}
newBullets := make([]multiplayerBullet, 0, len(state.Bullets))
for _, bullet := range state.Bullets {
bullet.X += bullet.VX * dt
bullet.Y += bullet.VY * dt
bullet.Life += dt
if bullet.Life > bulletLifetime {
continue
}
if bullet.X < 0 || bullet.X > arenaWidth || bullet.Y < 0 || bullet.Y > arenaHeight {
continue
}
hit := false
for id, player := range state.Players {
if !player.Alive || id == bullet.Owner {
continue
}
if overlaps(bullet.X, bullet.Y, player.X, player.Y, playerRadius+bulletRadius) {
player.Lives--
if player.Lives < 0 {
player.Lives = 0
}
if player.Lives <= 0 {
player.Alive = false
player.VX, player.VY = 0, 0
}
state.Players[id] = player
hit = true
break
}
}
if !hit {
newBullets = append(newBullets, bullet)
}
}
state.Bullets = newBullets
aliveCount := 0
lastAlive := ""
for id, player := range state.Players {
if player.Alive {
aliveCount++
lastAlive = id
}
}
if aliveCount == 1 {
state.Winner = lastAlive
} else if aliveCount == 0 {
state.Winner = ""
} else {
state.Winner = ""
}
}
{
if player, ok := state.Players[session]; ok {
return player
}
spawnX := playerRadius + rand.Float64()*(arenaWidth-2*playerRadius)
spawnY := playerRadius + rand.Float64()*(arenaHeight-2*playerRadius)
player := multiplayerPlayer{
ID: session,
X: spawnX,
Y: spawnY,
AimX: 0,
AimY: -1,
Color: pickColor(state),
Lives: maxLives,
Alive: true,
}
state.Players[session] = player
return player
}
{
used := make(map[string]bool, len(state.Players))
for _, player := range state.Players {
used[player.Color] = true
}
for _, color := range colorPalette {
if !used[color] {
return color
}
}
return colorPalette[rand.Intn(len(colorPalette))]
}
{
dx := ax - bx
dy := ay - by
return dx*dx+dy*dy <= radius*radius
}
{
switch val := v.(type) {
case float64:
return val
case float32:
return float64(val)
case int:
return float64(val)
case int64:
return float64(val)
}
return 0
}
{
switch val := v.(type) {
case bool:
return val
case float64:
return val != 0
case int:
return val != 0
}
return false
}
{
rand.Seed(time.Now().UnixNano())
}
RegisterNetcodeHost sets up a netcode server broadcasting position updates.
{
srv := netcode.NewServer("NetcodeHost", ncState{}, func(s *ncState, cmd any) {
if m, ok := cmd.(map[string]any); ok {
if dx, ok := m["dx"].(float64); ok {
s.X += dx
}
}
})
host.Register(srv.HostComponent())
go func() {
ticker := time.NewTicker(50 * time.Millisecond)
var tick int64
for range ticker.C {
tick += 50
srv.Broadcast(tick)
}
}()
}
{
var counter int
host.Register(host.NewHostComponent("SSCHost", func(_ map[string]any) any {
return map[string]any{"value": counter}
}))
go func() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
counter++
host.Broadcast("SSCHost", map[string]any{"value": counter})
}
}()
}
{
host.Register(host.NewHostComponent("TwitchOAuthHost", func(payload map[string]any) any {
code, _ := payload["code"].(string)
if code == "" {
return nil
}
fmt.Println("Payload:", payload)
client, err := helix.NewClient(&helix.Options{
ClientID: os.Getenv("TWITCH_CLIENT_ID"),
ClientSecret: os.Getenv("TWITCH_CLIENT_SECRET"),
RedirectURI: "https://localhost:8081/examples/twitch/callback",
})
if err != nil {
return map[string]any{"status": err.Error()}
}
resp, err := client.RequestUserAccessToken(code)
if err != nil {
return map[string]any{"status": err.Error()}
}
token := resp.Data.AccessToken
fmt.Println("Access token:", token)
if token == "" {
return map[string]any{"status": "missing token"}
}
return map[string]any{"status": "token received", "accessToken": token}
}))
}
import "math"
import "math/rand"
import "time"
import "github.com/rfwlab/rfw/v1/host"
import "github.com/rfwlab/rfw/v1/netcode"
import "time"
import "github.com/rfwlab/rfw/v1/host"
import "github.com/rfwlab/rfw/v1/netcode"
import "time"
import "github.com/rfwlab/rfw/v1/host"
import "fmt"
import "os"
import "github.com/nicklaw5/helix/v2"
helix
import "github.com/rfwlab/rfw/v1/host"