70c3677

Branches > facts > 70c3677 > files / src / net / bowmap

Add clientbound packets

Grahp, 2026-08-18

Commit: 70c3677d9d1008afd37a079e8d6b087c4ccc0f81
Parent: 66269b8

2 changed files:

src/net/bowmap/network/packets/clientbound.clj added (view file)

@@ -0,0 +1,281 @@+(ns net.bowmap.network.packets.clientbound+  (:require+   [net.bowmap.network.packet :as pk]+   [net.bowmap.network.packets.writers :as write+    :refer [bit-mask-> boolean-> byte-> chunk-data-> coerce context-optional->+            double-> enum-> float-> identifier-> int-> json-> length-prefixed+            light-data-> list-> long-> nbt-> optional-> or-> position->+            string-> ubyte-> uuid-> varint->]]+   [net.bowmap.network.registry :as reg]+   [net.bowmap.network.state :as state]+   [net.bowmap.server.gamemode :as gamemode])+  (:import+   [io.netty.buffer Unpooled]))++(def packet-order+  {::state/status+   [::status-response+    ::pong-response]+   ::state/login+   ; TODO do the message ids actually have to be different just because a different encoded format is used for the same logic message?+   [::login-disconnect ; Uses JSON instead of a text component :p+    ::encryption-request+    ::login-success+    ::set-compression+    ::login-plugin-request+    ::cookie-request]+   ::state/config+   [::cookie-request+    ::plugin-message+    ::disconnect+    ::finish-config+    ::keep-alive+    ::ping+    ::reset-chat+    ::registry-data+    ::remove-resource-pack+    ::add-resource-pack+    ::store-cookie+    ::transfer+    ::feature-flags+    ::update-tags+    ::known-packs+    ::custom-report-details+    ::server-links+    ::clear-dialog+    ::show-dialog+    ::code-of-conduct]+   ::state/play+   [::bundle-delimiter+    ::spawn-entity+    ::entity-animation+    ::award-statistics+    ::acknowledge-block-change+    ::set-block-destroy-stage+    ::block-entity-data+    ::block-action+    ::block-update+    ::boss-bar+    ::change-difficulty+    ::chunk-batch-finished+    ::chunk-batch-start+    ::chunk-biomes+    ::clear-titles+    ::command-suggestions-response+    ::commands+    ::close-container+    ::set-container-content+    ::set-container-property+    ::set-container-slot+    ::cookie-request+    ::set-cooldown+    ::chat-suggestions+    ::plugin-message+    ::damage-event+    ::debug-block-value+    ::debug-chunk-value+    ::debug-entity-value+    ::debug-event+    ::debug-sample+    ::delete-message+    ::disconnect+    ::disguised-chat-message+    ::entity-event+    ::teleport-entity+    ::explosion+    ::unload-chunk+    ::game-event+    ::game-test-highlight-position+    ::open-horse-screen+    ::hurt-animation+    ::initialize-world-border+    ::keep-alive+    ::chunk-data-and-update-light+    ::world-event+    ::particle+    ::update-light+    ::login+    ::map-data+    ::merchant-offers+    ::update-entity-position+    ::update-entity-position-and-rotation+    ::move-minecart-along-track+    ::update-entity-rotation+    ::move-vehicle+    ::open-book+    ::open-screen+    ::open-sign-editor+    ::ping+    ::pong-response+    ::place-ghost-recipe+    ::player-abilities+    ::player-chat-message+    ::end-combat+    ::enter-combat+    ::combat-death+    ::player-info-remove+    ::player-info-update+    ::look-at+    ::synchronize-player-position+    ::player-rotation+    ::recipe-book-add+    ::recipe-book-remove+    ::recipe-book-settings+    ::remove-entities+    ::remove-entity-effect+    ::reset-score+    ::remove-resource-pack+    ::add-resource-pack+    ::respawn+    ::set-head-rotation+    ::update-section-blocks+    ::select-advancements-tab+    ::server-data+    ::set-action-bar-text+    ::set-border-center+    ::set-border-lerp-size+    ::set-border-size+    ::set-border-warning-delay+    ::set-border-warning-distance+    ::set-camera+    ::set-center-chunk+    ::set-render-distance+    ::set-cursor-item+    ::set-default-spawn-position+    ::display-objective+    ::set-entity-metadata+    ::link-entities+    ::set-entity-velocity+    ::set-equipment+    ::set-experience+    ::set-health+    ::set-held-item+    ::update-objectives+    ::set-passengers+    ::set-player-inventory-slot+    ::update-teams+    ::update-score+    ::set-simulation-distance+    ::set-subtitle-text+    ::update-time+    ::set-title-text+    ::set-title-animation-times+    ::entity-sound-effect+    ::sound-effect+    ::start-configuration+    ::stop-sound+    ::store-cookie+    ::system-chat-message+    ::set-tab-list-header-and-footer+    ::tag-query-response+    ::pickup-item+    ::synchronize-vehicle-position+    ::test-instance-block-status+    ::set-ticking-state+    ::step-tick+    ::transfer+    ::update-advancements+    ::update-attributes+    ::entity-effect+    ::update-recipes+    ::update-tags+    ::projectile-power+    ::custom-report-details+    ::server-links+    ::waypoint+    ::clear-dialog+    ::show-dialog-play]})++(def packets+  {::status-response [:response "Response to serverbound ::status-request" json->]+   ::pong-response [:timestamp "The same timestamp as serverbound ::ping-request" long->]+   ::login-success [:uuid uuid->+                    :username string->+                    :properties (list-> [:name string->+                                         :value string->+                                         :signature (optional-> string->)])]+   ::known-packs [:known-packs (list-> [:namespace string->+                                        :id string->+                                        :version string->])]++   ::registry-data [::reg/id identifier->+                    ::reg/entries (list-> [::reg/entry-id identifier->+                                           ::reg/entry-data (optional-> nbt->)])]+   ::keep-alive [:id long->]+   ::finish-config []+   ::login [:entity-id int->+            :hardcore? boolean->+            :dimension-names (list-> identifier->)+            :max-players varint->+            :view-distance varint->+            :simulation-distance varint->+            :reduced-debug-info? boolean->+            :enable-respawn-screen? boolean->+            :do-limited-crafting? boolean->+            :dimension-type-id varint->+            :dimension-name identifier->+            :hashed-seed long->+            :gamemode (enum-> ubyte-> {0 ::gamemode/survival 1 ::gamemode/creative 2 ::gamemode/adventure 3 ::gamemode/spectator})+            :previous-gamemode (enum-> (or-> byte-> -1) {0 ::gamemode/survival 1 ::gamemode/creative 2 ::gamemode/adventure 3 ::gamemode/spectator})+            :debug-world? boolean->+            :superflat-world? boolean->+            :has-death-location? boolean->+            :death-dimension-name (context-optional-> identifier->)+            :death-location (context-optional-> position->)+            :portal-cooldown varint->+            :sea-level varint->+            :enforce-secure-chat? boolean->]+   ;; And others too but we don't talk about those+   ::game-event [:event (enum-> byte-> {3 :set-gamemode 13 :wait-for-chunks})+                 :value (or-> float-> 0)]+   ::chunk-data-and-update-light [:chunk-x int->+                                  :chunk-z int->+                                  :chunk-data chunk-data->+                                  :light-data light-data->]+   ::synchronize-player-position [:teleport-id varint->+                                  ; TODO might want to extrac this into its own type(s)?+                                  :x double->+                                  :y double->+                                  :z double->+                                  :velocity-x double->+                                  :velocity-y double->+                                  :velocity-z double->+                                  :yaw float->+                                  :pitch float->+                                  :flags (bit-mask-> int->+                                                     [:relative-x :relative-y :relative-z+                                                      :relative-yaw :relative-pitch+                                                      :relative-velocity-x :relative-velocity-y :relative-velocity-z+                                                      :rotate-velocity])]})++(defn build-packet-id-index+  "Builds a map of connection states and message ids to packet ids.+   Message ids may be duplicated across connection states."+  [registry]+  (into {}+        (for [[state packet-ids] registry+              [i id] (map-indexed vector packet-ids)]+          [{::state/connection-state state+            ::pk/id id}+           i])))++(defn build-packet-encode-functions+  "Builds a map of message ids to message writers."+  [registry]+  (reduce (fn [acc [packet-id table]]+            (assoc acc packet-id (coerce table)))+          {} registry))++(def packet-ids (build-packet-id-index packet-order))+(def packet-encoders (build-packet-encode-functions packets))++(defn encode-packet!+  "Encodes a message into a packet.+   Like decoding, encoding a packet requires the connection state, as packet+   ids are not unique across connection states. Several packets are logically+   the same across states, and can be sent without needing to switch on state."+  [packet state]+  (let [payload-writer (req! packet-encoders (::pk/id packet))]+    (length-prefixed (-> (Unpooled/buffer)+                         (varint-> (req! packet-ids {::pk/id (::pk/id packet) ::state/connection-state state}))+                         (payload-writer packet)))))

src/net/bowmap/network/packets/writers.clj added (view file)

@@ -0,0 +1,281 @@+(ns net.bowmap.network.packets.writers+  "Functions to write Minecraft protocol types.+   When given bad data, write functions will throw.+   See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"+  (:require+   [clojure.spec.alpha :as s]+   [clojure.data.json :as json]+   [clojure.set :as set]+   [net.bowmap.network.packets.types :as t])+  (:import+   [io.netty.buffer ByteBuf CompositeByteBuf Unpooled]+   [java.nio.charset Charset]+   [java.util BitSet UUID]))++(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 length-prefixed+  "Returns a new ByteBuf prefixed by an encoded varint of the length of 'buf'."+  [buf]+  (-> (Unpooled/compositeBuffer 2)+      (CompositeByteBuf/.addComponent true (varint-> (Unpooled/buffer t/max-varint-size) (ByteBuf/.writerIndex buf)))+      (CompositeByteBuf/.addComponent true buf)))++(defn string-> [buf s]+  (doto buf+    (varint-> (count s))+    (ByteBuf/.writeCharSequence s (Charset/forName "UTF-8"))))++(defn json-> [buf s]+  (string-> buf (json/write-str s)))++(defn uuid-> [buf uuid]+  (-> buf+      (ByteBuf/.writeLong (UUID/.getMostSignificantBits uuid))+      (ByteBuf/.writeLong (UUID/.getLeastSignificantBits uuid))))++; TODO This is where you could check for optionality I think. If the fields aren't [:optional ...]+; TODO Inline+(defn ^:private write-table! [buf table m]+  (reduce (fn [acc {::t/keys! [field codec]}]+            (codec acc (get m field)))+          buf table))++(defn boolean-> [buf x]+  (if x+    (ByteBuf/.writeByte buf 1)+    (ByteBuf/.writeByte buf 0)))++(defn context-optional-> [writer]+  (fn [buf x]+    (if x+      (writer buf x)+      buf)))++; Want to conform the given map to the table, and error if any non-optional keys are missing?+(defn table-> [t]+  (assert (s/valid? ::t/table t))+  (let [table (s/conform ::t/table t)]+    (fn [buf m]+      (write-table! buf table m))))++(defn coerce+  "If writer is an actual writer, returns it. If writer is a vector, turnes it into a table writer."+  [writer]+  (if (vector? writer)+    (table-> writer)+    writer))++; Only coerces these for now idk+(defn array-> [writer]+  (let [writer (coerce writer)]+    (fn [buf xs]+      (reduce (fn [acc x]+                (writer acc x))+              buf xs))))++(defn list-> [writer]+  (let [writer (array-> writer)]+    (fn [buf xs]+      (-> (varint-> buf (count xs))+          (writer xs)))))++;;; NBT++(defn write-modified-utf8! [buf s]+  (reduce (fn [acc code]+            (cond (zero? code) (-> buf+                                   (ByteBuf/.writeByte 0xC0)+                                   (ByteBuf/.writeByte 0x80))+                  (<= 1 code 0x7F) (ByteBuf/.writeByte acc code)+                  (<= 0x80 code 0x7FF) (-> acc

Show all 282 lines