client.clj

Branches > namespace-reorganization > 9ac197d > files / src / net / bowmap / network / message / client.clj

(ns net.bowmap.network.message.client
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.build :refer [build]]
   [dev.grahp.error :refer [adorn get!]]
   [net.bowmap.network.message :as msg]
   [net.bowmap.network.packet :as pk]
   [net.bowmap.network.packet.types :as t]
   [net.bowmap.network.packet.write :as write
    :refer [bit-mask-> boolean-> byte-> chunk-data-> context-optional->
            double-> enum-> float-> identifier-> int-> json-> length-prefixed
            light-data-> list-> long-> nbt-> optional-> or-> position->
            string-> table-> ubyte-> uuid-> varint->]]
   [net.bowmap.network.state :as state])
  (:import
   [io.netty.buffer Unpooled]))

(defn build-packet-ids [registry]
  (build [[state packet-ids] registry
          [i id] (map-indexed vector packet-ids)]
    [{::state/connection-state state
      ::msg/id id}
     i]))

(s/fdef build-packet-ids
        :args (s/cat :registry (s/map-of ::state/connection-state (s/coll-of ::msg/id :type vector?)))
        :ret (s/map-of (s/schema [::state/connection-state ::msg/id]) ::pk/id))

(defn build-packets [registry]
  (reduce (fn [acc [packet-id table]]
            (assoc acc packet-id (table-> table)))
          {} registry))

(s/fdef build-packets
        :args (s/cat (s/map-of ::msg/id ::t/table))
        :ret (s/map-of ::msg/id ::write/writer))

(def ^{:spec ::message->packet-ids}
  message->packet-ids
  (build-packet-ids
    {::state/status
     [::status-response
      ::pong-response]
     ::state/login
     ;; TODO just because the serialized form is different doesn't mean they have to be different message ids...
     [::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
  (build-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-> (table-> [:name string->
                                                    :value string->
                                                    :signature (optional-> string->)]))]
     ::known-packs [:known-packs (list-> (table-> [:namespace string->
                                                   :id string->
                                                   :version string->]))]

     ::registry-data [:registry-id identifier->
                      :entries (list-> (table-> [:id identifier->
                                                 :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->
              ;; TODO :net.bowmap.gamemode ig?
              :gamemode (enum-> ubyte-> {0 :survival 1 :creative 2 :adventure 3 :spectator})
              :previous-gamemode (enum-> byte-> {-1 :unknown 0 :survival 1 :creative 2 :adventure 3 :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->
                                    :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 message->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."
  [msg state]
  (-> (let [encode-body (get! packets (::msg/id msg))]
        (length-prefixed (-> (Unpooled/buffer)
                             (varint-> (get! message->packet-ids {::msg/id (::msg/id msg) ::state/connection-state state}))
                             (encode-body msg))))
      (adorn "Error while encoding message into packet" {:msg msg :state state})))

(s/fdef message->packet
        :args (s/cat :msg ::msg/message :state ::state/connection-state)
        :ret ::pk/packet)