client.clj
Branches > namespace-reorganization > da5bbf0 > 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!]]
[dev.grahp.spec :as S]
[net.bowmap.network.message :as msg]
[net.bowmap.network.packet :as pk]
[net.bowmap.network.packet.types :as t]
[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]))
(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 ::message->packet-ids)
(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 ::packets)
(S/def ::message->packet-ids (s/map-of (s/schema [::state/connection-state ::msg/id]) ::pk/id))
(def message->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]}))
(S/def ::packets
(s/map-of ::msg/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %) :pk ::msg/message)
:ret #(instance? ByteBuf %))))
(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)]}))
(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})))