clientbound.clj
Branches > main > b4e6bf9 > files / src / net / bowmap / protocol / packet / clientbound.clj
(ns net.bowmap.protocol.packet.clientbound
(:require
[net.bowmap.protocol.packet :as pk]
[net.bowmap.protocol.packet.writers :as write
:refer [bit-mask-> boolean-> byte-> chunk-data-> coerce context-optional->
double-> enum-> float-> game-profile-> identifier-> int-> json->
length-prefixed light-data-> list-> long-> nbt-> optional-> or->
position-> string-> ubyte-> uuid-> varint->]]
[net.bowmap.protocol.registry :as reg]
[net.bowmap.protocol.state :as state]
[net.bowmap.server.gamemode :as gamemode])
(:import
[io.netty.buffer Unpooled]))
(def packet-order
{::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-rule-values
::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
::low-disk-space-warning
::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
{::status-response [:response "Response to serverbound ::status-request" json->]
::pong-response [:timestamp "The same timestamp as serverbound ::ping-request" long->]
::login-success [:profile game-profile->
:session-id uuid->]
; TODO these use text components, can do that later
::login-disconnect [:reason json->]
::disconnect [:reason nbt->]
::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->)])]
::recipe-book-settings [:crafting-recipe-book-open? boolean->
:crafting-recipe-book-filter-active? boolean->
:smelting-recipe-book-open? boolean->
:smelting-recipe-book-filter-active? boolean->
:blast-furnace-recipe-book-open? boolean->
:blast-furnace-recipe-book-filter-active? boolean->
:smoker-recipe-book-open? boolean->
:smoker-recipe-book-filter-active? boolean->]
::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->
:online-mode? boolean->
: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->]
; TODO this is for teleporting, it's kind of a terrible name :p
::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])]
::update-tags [:tagged-registries (list-> [:registry identifier->
:tags (list-> [:tag-name identifier->
:entries (list-> varint->)])])]})
(defn build-packet-id-index
"Builds a map of connection states and message ids to packet ids.
Message ids may be duplicated across connection states."
[registry]
(into {}
(for [[state packet-ids] registry
[i id] (map-indexed vector packet-ids)]
[{::state/connection-state state
::pk/id id}
i])))
(defn build-packet-encode-functions
"Builds a map of message ids to message writers."
[registry]
(reduce (fn [acc [packet-id table]]
(assoc acc packet-id (coerce table)))
{} registry))
(def packet-ids (build-packet-id-index packet-order))
(def packet-encoders (build-packet-encode-functions packets))
(defn encode-packet!
"Encodes a packet into a frame.
Requires the connection state, as packet ids are not unique across connection states.
Several packets are logically the same across states."
[packet state]
(let [payload-writer (req! packet-encoders (::pk/id packet))]
(length-prefixed (-> (Unpooled/buffer)
(varint-> (req! packet-ids {::pk/id (::pk/id packet) ::state/connection-state state}))
(payload-writer packet)))))