clientbound.clj
Branches > main > 599e72a > files / src / net / bowmap / network / protocol / message / clientbound.clj
(ns net.bowmap.network.protocol.message.clientbound
"Schemas for messages sent from the server to the client"
(:require
[clojure.spec.alpha :as s]
[dev.grahp.build :refer [build]]
[net.bowmap.network.protocol.message :as msg]
[net.bowmap.network.protocol.frame :as frame]
[net.bowmap.network.protocol.types :as t]
[net.bowmap.network.protocol.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.protocol.registry :as reg]
[net.bowmap.network.protocol.state :as state]
[net.bowmap.server.gamemode :as gamemode])
(:import
[io.netty.buffer Unpooled]))
(defn build-numeric-ids
"Builds a map of connection states and message ids to numeric ids.
Message ids may be duplicated across connection states."
[registry]
(build [[state numeric-ids] registry
[i id] (map-indexed vector numeric-ids)]
[{::state/connection-state state
::msg/id id}
i]))
(s/fdef build-numeric-ids
:args (s/cat :registry (s/map-of ::state/connection-state (s/coll-of ::msg/id :type vector?)))
:ret (s/map-of (s/keys :opt [::state/connection-state ::msg/id]) ::msg/numeric-id))
(defn build-messages
"Builds a map of message ids to message writers."
[registry]
(reduce (fn [acc [numeric-id table]]
(assoc acc numeric-id (coerce table)))
{} registry))
(s/fdef build-messages
:args (s/cat :registry (s/map-of ::msg/id ::t/table))
:ret (s/map-of ::msg/id ::write/writer))
; TODO terrible name?
(def numeric-id
(build-numeric-ids
{::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?
; (no)
[::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 messages
(build-messages
{::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})
; -1 means the gamemode is unknown
: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 message->frame
"Encodes a message into a frame.
Like decoding, encoding a message requires the connection state, as message
ids are not unique across connection states. Several messages are logically
the same across states, and can be sent without needing to switch on state."
[msg state]
(let [payload-writer (req! messages (::msg/id msg))]
(length-prefixed (-> (Unpooled/buffer)
(varint-> (req! numeric-id {::msg/id (::msg/id msg) ::state/connection-state state}))
(payload-writer msg)))))
(s/fdef message->frame
:args (s/cat :msg ::msg/message :state ::state/connection-state)
:ret ::frame/frame)