clientbound.clj

Branches > facts > b4e6bf9 > files / src / net / bowmap / protocol / packet / clientbound.clj

(ns net.bowmap.protocol.packet.clientbound
  (:require
   [net.bowmap.protocol.packet :as pk]
   [net.bowmap.protocol.packet.writers :as write
    :refer [bit-mask-> boolean-> byte-> chunk-data-> coerce context-optional->
            double-> enum-> float-> game-profile-> identifier-> int-> json->
            length-prefixed light-data-> list-> long-> nbt-> optional-> or->
            position-> string-> ubyte-> uuid-> varint->]]
   [net.bowmap.protocol.registry :as reg]
   [net.bowmap.protocol.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-rule-values
    ::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
    ::low-disk-space-warning
    ::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 [:profile game-profile->
                    :session-id uuid->]
   ; TODO these use text components, can do that later
   ::login-disconnect [:reason json->]
   ::disconnect [:reason nbt->]
   ::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->)])]
   ::recipe-book-settings [:crafting-recipe-book-open? boolean->
                           :crafting-recipe-book-filter-active? boolean->
                           :smelting-recipe-book-open? boolean->
                           :smelting-recipe-book-filter-active? boolean->
                           :blast-furnace-recipe-book-open? boolean->
                           :blast-furnace-recipe-book-filter-active? boolean->
                           :smoker-recipe-book-open? boolean->
                           :smoker-recipe-book-filter-active? boolean->]
   ::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->
            :online-mode? boolean->
            :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->]
   ; TODO this is for teleporting, it's kind of a terrible name :p
   ::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])]
   ::update-tags [:tagged-registries (list-> [:registry identifier->
                                              :tags (list-> [:tag-name identifier->
                                                             :entries (list-> varint->)])])]})

(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 packet into a frame.
   Requires the connection state, as packet ids are not unique across connection states.
   Several packets are logically the same across states."
  [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)))))