70c3677
Branches > facts > 70c3677 > files / src / net / bowmap
Add clientbound packets
Commit: 70c3677d9d1008afd37a079e8d6b087c4ccc0f81
Parent: 66269b8
2 changed files:
src/net/bowmap/network/packets/clientbound.clj 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 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