summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rwxr-xr-xserver/cyberjumpbin0 -> 9442714 bytes
-rw-r--r--server/go.mod7
-rw-r--r--server/go.sum4
-rw-r--r--server/main.go263
4 files changed, 274 insertions, 0 deletions
diff --git a/server/cyberjump b/server/cyberjump
new file mode 100755
index 0000000..00d4ec1
--- /dev/null
+++ b/server/cyberjump
Binary files differ
diff --git a/server/go.mod b/server/go.mod
new file mode 100644
index 0000000..5ef24cc
--- /dev/null
+++ b/server/go.mod
@@ -0,0 +1,7 @@
+module cyberjump
+
+go 1.21
+
+require github.com/gorilla/websocket v1.5.1
+
+require golang.org/x/net v0.17.0 // indirect
diff --git a/server/go.sum b/server/go.sum
new file mode 100644
index 0000000..272772f
--- /dev/null
+++ b/server/go.sum
@@ -0,0 +1,4 @@
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
diff --git a/server/main.go b/server/main.go
new file mode 100644
index 0000000..cd31356
--- /dev/null
+++ b/server/main.go
@@ -0,0 +1,263 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "net/http"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+type playerState struct {
+ ID string `json:"id"`
+ X float64 `json:"x"`
+ Y float64 `json:"y"`
+ VX float64 `json:"vx"`
+ VY float64 `json:"vy"`
+ Height float64 `json:"height"`
+ Layer string `json:"layer"`
+ Cycle int `json:"cycle"`
+ UpdatedAt int64 `json:"updatedAt"`
+}
+
+type stateMessage struct {
+ Type string `json:"type"`
+ X float64 `json:"x"`
+ Y float64 `json:"y"`
+ VX float64 `json:"vx"`
+ VY float64 `json:"vy"`
+ Height float64 `json:"height"`
+ Layer string `json:"layer"`
+ Cycle int `json:"cycle"`
+}
+
+type welcomeMessage struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+}
+
+type snapshotMessage struct {
+ Type string `json:"type"`
+ Players []playerState `json:"players"`
+}
+
+type client struct {
+ id string
+ conn *websocket.Conn
+ hub *hub
+ mu sync.Mutex
+}
+
+type hub struct {
+ mu sync.RWMutex
+ clients map[string]*client
+ states map[string]playerState
+ ids atomic.Uint64
+}
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+func newHub() *hub {
+ return &hub{
+ clients: make(map[string]*client),
+ states: make(map[string]playerState),
+ }
+}
+
+func (h *hub) addClient(conn *websocket.Conn) *client {
+ id := fmt.Sprintf("ghost-%04d", h.ids.Add(1))
+ client := &client{id: id, conn: conn, hub: h}
+
+ h.mu.Lock()
+ h.clients[id] = client
+ h.mu.Unlock()
+
+ return client
+}
+
+func (h *hub) removeClient(id string) {
+ h.mu.Lock()
+ delete(h.clients, id)
+ delete(h.states, id)
+ h.mu.Unlock()
+ h.broadcastSnapshot()
+}
+
+func (h *hub) updateState(id string, message stateMessage) {
+ h.mu.Lock()
+ h.states[id] = playerState{
+ ID: id,
+ X: message.X,
+ Y: message.Y,
+ VX: message.VX,
+ VY: message.VY,
+ Height: message.Height,
+ Layer: message.Layer,
+ Cycle: message.Cycle,
+ UpdatedAt: time.Now().UnixMilli(),
+ }
+ h.mu.Unlock()
+ h.broadcastSnapshot()
+}
+
+func (h *hub) snapshot() []playerState {
+ h.mu.RLock()
+ defer h.mu.RUnlock()
+
+ players := make([]playerState, 0, len(h.states))
+ for _, state := range h.states {
+ players = append(players, state)
+ }
+ return players
+}
+
+func (h *hub) clientCount() int {
+ h.mu.RLock()
+ defer h.mu.RUnlock()
+ return len(h.clients)
+}
+
+func (h *hub) broadcastSnapshot() {
+ payload, err := json.Marshal(snapshotMessage{Type: "snapshot", Players: h.snapshot()})
+ if err != nil {
+ log.Printf("snapshot marshal error: %v", err)
+ return
+ }
+
+ h.mu.RLock()
+ clients := make([]*client, 0, len(h.clients))
+ for _, connected := range h.clients {
+ clients = append(clients, connected)
+ }
+ h.mu.RUnlock()
+
+ for _, connected := range clients {
+ connected.writeJSON(payload)
+ }
+}
+
+func (c *client) writeJSON(payload []byte) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if err := c.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
+ log.Printf("write error for %s: %v", c.id, err)
+ }
+}
+
+func (c *client) sendWelcome() error {
+ payload, err := json.Marshal(welcomeMessage{Type: "welcome", ID: c.id})
+ if err != nil {
+ return err
+ }
+ c.writeJSON(payload)
+ return nil
+}
+
+func (c *client) readLoop() {
+ defer func() {
+ c.hub.removeClient(c.id)
+ _ = c.conn.Close()
+ }()
+
+ if err := c.sendWelcome(); err != nil {
+ log.Printf("welcome error for %s: %v", c.id, err)
+ return
+ }
+
+ for {
+ _, payload, err := c.conn.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+ log.Printf("read error for %s: %v", c.id, err)
+ }
+ return
+ }
+
+ var message stateMessage
+ if err := json.Unmarshal(payload, &message); err != nil {
+ continue
+ }
+
+ if message.Type != "state" {
+ continue
+ }
+
+ c.hub.updateState(c.id, message)
+ }
+}
+
+func main() {
+ addr := flag.String("addr", ":8080", "HTTP service address")
+ flag.Parse()
+
+ h := newHub()
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Printf("websocket upgrade error: %v", err)
+ return
+ }
+
+ client := h.addClient(conn)
+ go client.readLoop()
+ })
+
+ mux.HandleFunc("/ping", func(w http.ResponseWriter, _ *http.Request) {
+ withCORS(w)
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("pong"))
+ })
+
+ mux.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) {
+ withCORS(w)
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "status": "online",
+ "players": h.clientCount(),
+ })
+ })
+
+ mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+ withCORS(w)
+ w.Header().Set("Content-Type", "application/json")
+
+ host := r.Host
+ if host == "" {
+ host = "localhost:8080"
+ }
+
+ scheme := "ws"
+ if r.TLS != nil {
+ scheme = "wss"
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "servers": []map[string]string{{
+ "url": fmt.Sprintf("%s://%s/ws", scheme, host),
+ "region": "local",
+ }},
+ })
+ })
+
+ log.Printf("CyberJump relay listening on %s", *addr)
+ if err := http.ListenAndServe(*addr, mux); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func withCORS(w http.ResponseWriter) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+}