package main import ( "bytes" "encoding/binary" "log" "math/rand" "net/http" "sync" "time" "github.com/gorilla/websocket" ) const ( PacketHandshake uint8 = 0 PacketWorld uint8 = 1 TickRate = 30 Speed = 5.0 SpawnMinX = -5.0 SpawnMaxX = 5.0 SpawnMinZ = -5.0 SpawnMaxZ = 5.0 SpawnRadius = 1.5 ) type Player struct { ID uint32 X float32 Y float32 Z float32 InputX float32 InputZ float32 Conn *websocket.Conn } var ( upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } players = make(map[uint32]*Player) playersMu sync.Mutex nextID uint32 = 1 ) func main() { rand.Seed(time.Now().UnixNano()) http.HandleFunc("/ws", handleWS) go gameLoop() log.Println("Server running on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } playersMu.Lock() id := nextID nextID++ x, z := randomSpawnPosition() player := &Player{ ID: id, X: x, Y: 0, Z: z, Conn: conn, } players[id] = player playersMu.Unlock() { buf := new(bytes.Buffer) binary.Write(buf, binary.LittleEndian, PacketHandshake) binary.Write(buf, binary.LittleEndian, id) conn.WriteMessage(websocket.BinaryMessage, buf.Bytes()) } log.Println("Player connected:", id, "spawn at", x, z) go readLoop(player) } func randomSpawnPosition() (float32, float32) { for i := 0; i < 20; i++ { x := rand.Float32()*(SpawnMaxX-SpawnMinX) + SpawnMinX z := rand.Float32()*(SpawnMaxZ-SpawnMinZ) + SpawnMinZ if isSpawnFree(x, z) { return x, z } } // fallback return 0, 0 } func isSpawnFree(x, z float32) bool { for _, p := range players { dx := p.X - x dz := p.Z - z if dx*dx+dz*dz < SpawnRadius*SpawnRadius { return false } } return true } func readLoop(p *Player) { defer func() { playersMu.Lock() delete(players, p.ID) playersMu.Unlock() p.Conn.Close() log.Println("Player disconnected:", p.ID) }() for { _, data, err := p.Conn.ReadMessage() if err != nil { return } if len(data) < 8 { continue } buf := bytes.NewReader(data) binary.Read(buf, binary.LittleEndian, &p.InputX) binary.Read(buf, binary.LittleEndian, &p.InputZ) } } func gameLoop() { ticker := time.NewTicker(time.Second / TickRate) defer ticker.Stop() dt := float32(1.0 / TickRate) for range ticker.C { update(dt) broadcast() } } func update(dt float32) { playersMu.Lock() defer playersMu.Unlock() for _, p := range players { p.X += p.InputX * Speed * dt p.Z += p.InputZ * Speed * dt } } func broadcast() { playersMu.Lock() defer playersMu.Unlock() buf := new(bytes.Buffer) binary.Write(buf, binary.LittleEndian, PacketWorld) binary.Write(buf, binary.LittleEndian, uint16(len(players))) for _, p := range players { binary.Write(buf, binary.LittleEndian, p.ID) binary.Write(buf, binary.LittleEndian, p.X) binary.Write(buf, binary.LittleEndian, p.Y) binary.Write(buf, binary.LittleEndian, p.Z) } data := buf.Bytes() for _, p := range players { p.Conn.WriteMessage(websocket.BinaryMessage, data) } }