client.clj

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

; TODO
(ns net.bowmap.network.message.client
  "Clientbound packets"
  (: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.write :refer [boolean-> byte->
                                            context-optional-> enum-> float->
                                            identifier-> int-> json->
                                            length-prefixed list-> long->
                                            nbt-> optional-> or-> position->
                                            string-> table-> ubyte-> uuid->
                                            varint->]]
   [net.bowmap.network.state :as state])
  (:import
   [io.netty.buffer ByteBuf Unpooled]))

; TODO docstrings!
(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]))

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

; TODO docstring?
(def ^{:spec (s/map-of (s/schema [::state/connection-state ::msg/id]) ::pk/id)}
  packet-ids
  ; Packet ids could map directly to numeric-ids and be stateless,
  ; but some packets have duplicates across connection states that you'd want to handle in the same way.
  ; If it turns out that I don't end up handling packets in this way, then packet ids could map directly to numeric 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 ^{:spec (s/map-of ::msg/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %) :pk ::msg/message)
                                        :ret #(instance? ByteBuf %)))}
  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)]}))

(defn message->packet
  "Encodes packet 'pk' into a ByteBuf of the packet bytes.
   Like decoding, encoding a packet also requires the connection state, as
    packet ids are not unique across connection state, so there isn't a
    direct mapping of packet ids to numeric ids.
   There are several packets (disconnect, keep alive) that are logically the
    same across connection states, and so can be sent without needing to switch
    on the connection state."
  [msg state]
  (-> (let [encode-body (get! packets msg)]
        (length-prefixed (-> (Unpooled/buffer)
                             (varint-> (get! packet-ids msg state))
                             (encode-body msg))))
      (adorn "Error while encoding message into packet" {:msg msg :state state})))