clientbound.clj
Branches > main > 84b2f10 > files / src / net / bowmap / network / protocol / message / clientbound.clj
(ns net.bowmap.network.protocol.message.clientbound
(:require
[clojure.alpha.spec :as s]
[dev.grahp.build :refer [build]]
[net.bowmap.network.protocol.message :as msg]
[net.bowmap.network.protocol.packet :as pk]
[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-packet-ids
"Builds a map of connection states and message ids to packet ids.
Message ids may be duplicated across connection states."
[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]) ::msg/numeric-id))
(defn build-packets
"Builds a map of message ids to message writers."
[registry]
(reduce (fn [acc [packet-id table]]
(assoc acc packet-id (coerce 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 message->packet-ids
(build-packet-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?
[::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-> [: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})
: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->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 [payload-writer (req! packets (::msg/id msg))]
(length-prefixed (-> (Unpooled/buffer)
(varint-> (req! message->packet-ids {::msg/id (::msg/id msg) ::state/connection-state state}))
(payload-writer msg)))))
(s/fdef message->packet
:args (s/cat :msg ::msg/message :state ::state/connection-state)
:ret ::pk/packet)