writers.clj

Bowmap · bowmap.git · Tickets

Branches > main > 30cd6b9 > files / src / net / bowmap / server / protocol / writers.clj

(ns net.bowmap.server.protocol.writers
  "Functions for writing values into io.netty.ByteBufs.
   Functions look like type>, where 'type' is a Minecraft protocol type.
   See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"
  (:require
   [clojure.data.json :as json]
   [clojure.spec.alpha :as s]
   [net.bowmap.server.protocol.schema :as p])
  (:import
   [io.netty.buffer ByteBuf]
   [java.nio.charset Charset]))

(s/def ::writer (s/fspec
                  :args (s/cat :frame ::frame :value any?)
                  :fn #(= (-> % :args :frame)
                          (:ret %))))

(def byte> ByteBuf/.writeByte)
(def ubyte> ByteBuf/.writeByte)
(def short> ByteBuf/.writeShort)
(def ushort> ByteBuf/.writeShort)
(def int> ByteBuf/.writeInt)
(def long> ByteBuf/.writeLong)
(def float> ByteBuf/.writeFloat)
(def double> ByteBuf/.writeDouble)

(defn varint> [buf n]
  (loop [buf buf
         n n]
    (let [b (bit-and n 0x7F)
          nxt (unsigned-bit-shift-right n 7)]
      (if (zero? nxt)
        (ByteBuf/.writeByte buf b)
        (recur (ByteBuf/.writeByte buf (bit-or b 0x80))
               nxt)))))

(defn string> [buf s]
  {:pre [(string? s)]}
  (doto buf
    (varint> (count s))
    (ByteBuf/.writeCharSequence s (Charset/forName "UTF-8"))))

(defn json> [buf s]
  (string> buf (json/write-str s)))

(defn status-response> [buf {::p/keys! [version-name protocol]
                              ::p/keys [motd max-player-count online-player-count]}]
  (json> buf
          (cond-> {:version {:name version-name
                             :protocol protocol}}
            motd (assoc :description motd)
            (and max-player-count online-player-count) (assoc :players {:max max-player-count
                                                                        :online online-player-count}))))

(defn table> [table]
  (let [t (partition 2 table)]
    (fn [buf m]
      (reduce (fn [acc [field writer]]
                (writer acc (get m field)))
              buf t))))

(defn ->writer
  "Attempts to coerces 'x' into a writer. Throws if it cannot.
   Vectors with pairs of [:key writer] are turned into tables writers.
   Nil is not a valid writer."
  [x]
  (cond
    (vector? x) (table> x)
    (fn? x) x
    :else (throw (IllegalArgumentException. (str "Cannot coerce into writer: " (pr-str x))))))