handle.clj
Branches > main > ec993cd > files / src / net / bowmap / network / handle.clj
(ns net.bowmap.network.handle
(:require
[net.bowmap.network.chunk :as chunk]
[net.bowmap.network.client :as client]
[net.bowmap.network.packet :as pk]
[net.bowmap.network.packets.clientbound :as clientbound]
[net.bowmap.network.packets.registry :as registry]
[net.bowmap.network.packets.serverbound :as serverbound :refer [decode-packet!]]
[net.bowmap.network.state :as state]
[net.bowmap.server.gamemode :as gamemode]))
; Handles incoming packets from clients
(defmulti handler
(fn [client client-agent packet]
(::pk/id packet)))
(defmethod handler :default [client a pk]
(prn "Unhandled packet:" pk)
client)
(defmethod handler ::serverbound/handshaking [client _ pk]
(assoc client
::state/connection-state (req! pk :intent)
:protocol-id (req! pk :protocol)))
(defmethod handler ::serverbound/status-request [client a _]
(send a client/send-clientbound-packet
{::pk/id ::clientbound/status-response
;; Should be un-bundled yeah.
:response {:version {:name "26.2"
:protocol 776}
:players {:online 0
:max 1000}
; Is this a text component? Could make that interface
:description {:text "Bowmap!"}
:enforcesSecureChat false}})
client)
(defmethod handler ::serverbound/ping-request [client a pk]
(send a client/send-clientbound-packet
{::pk/id ::clientbound/pong-response
:timestamp (:timestamp pk)})
client)
(defmethod handler ::serverbound/login-start [client a pk]
(let [{:keys! [uuid username]} pk]
(send a client/send-clientbound-packet
{::pk/id ::clientbound/login-success
:profile {:uuid uuid
:username username
:properties []}
:session-id (random-uuid)}))
client)
(defmethod handler ::serverbound/login-acknowledged [client a pk]
(send a client/send-clientbound-packet
{::pk/id ::clientbound/known-packs
;; TODO also probably good to unbundle this
:known-packs [{:namespace "minecraft"
:id "core"
:version "26.2"}]})
(assoc client ::state/connection-state ::state/config))
(defmethod handler ::serverbound/known-packs [client a pk]
(doseq [pk (registry/registry-packets)]
(send a client/send-clientbound-packet pk))
(send a client/send-clientbound-packet {::pk/id ::clientbound/update-tags
:tagged-registries [{:registry "minecraft:block"
:tags [{:tag-name "minecraft:infiniburn_overworld"
:entries []}]}]})
(send a client/send-clientbound-packet {::pk/id ::clientbound/update-tags
:tagged-registries [{:registry "minecraft:damage_type"
:tags [{:tag-name "minecraft:is_fire"
:entries []}
{:tag-name "minecraft:is_explosion"
:entries []}
{:tag-name "minecraft:bypasses_shield"
:entries []}]}]})
(send a client/send-clientbound-packet {::pk/id ::clientbound/update-tags
:tagged-registries [{:registry "minecraft:banner_pattern"
:tags [{:tag-name "minecraft:pattern_item/bordure_indented" :entries []}
{:tag-name "minecraft:pattern_item/creeper" :entries []}
{:tag-name "minecraft:pattern_item/field_masoned" :entries []}
{:tag-name "minecraft:pattern_item/flow" :entries []}
{:tag-name "minecraft:pattern_item/flower" :entries []}
{:tag-name "minecraft:pattern_item/globe" :entries []}
{:tag-name "minecraft:pattern_item/guster" :entries []}
{:tag-name "minecraft:pattern_item/mojang" :entries []}
{:tag-name "minecraft:pattern_item/piglin" :entries []}
{:tag-name "minecraft:pattern_item/skull" :entries []}]}]})
(send a client/send-clientbound-packet {::pk/id ::clientbound/finish-config})
client)
(defn- build-chunk-messages []
(let [pk {::pk/id ::clientbound/chunk-data-and-update-light
:chunk-data
(let [stone {:block-count (* 16 16 16)
:fluid-count 0
:block-states {:bpe 0
:palatte 1}
:biomes {:bpe 0
:palatte 0}}
air {:block-count 0
:fluid-count 0
:block-states {:bpe 0
:palatte 0}
:biomes {:bpe 0
:palatte 0}}]
{:data (cons stone (repeat (- chunk/world-height-chunk-sections 1) air))})
:light-data {}}]
(for [x (range -16 17)
z (range -16 17)]
(assoc pk
:chunk-x x
:chunk-z z))))
(defmethod handler ::serverbound/finish-config [client a pk]
(send a client/send-clientbound-packet
{::pk/id ::clientbound/login
;; TODO magic one :p
;; I think you can keep a registry of entity ids and stick this on the client
:entity-id 1
:hardcore? false
:dimension-names ["world"]
:max-players 1000 ; TODO extract :p
:view-distance 32
:simulation-distance 32
:reduced-debug-info? false
:enable-respawn-screen? false
:do-limited-crafting? false
:dimension-type-id 0
:dimension-name "world"
:hashed-seed 203894
:gamemode ::gamemode/creative
:debug-world? false
:superflat-world? true
:has-death-location? false
:portal-cooldown 10
:sea-level 60
:online-mode? false
:enforce-secure-chat? true})
(send a client/send-clientbound-packet
{::pk/id ::clientbound/game-event
:event :wait-for-chunks})
(doseq [pk (build-chunk-messages)]
(send a client/send-clientbound-packet pk))
(send a client/send-clientbound-packet
{::pk/id ::clientbound/synchronize-player-position
:teleport-id (rand-int Integer/MAX_VALUE)
:x 0
:y 16
:z 0
:velocity-x 0
:velocity-y 0
:velocity-z 0
:yaw 0
:pitch 0
:flags #{}})
(assoc client ::state/connection-state ::state/play))
; TODO keepalive
; Should probably return the packets to be sent out?
; I don't liek that the agent is in here.
(defn handle-packet
"Handles a severbound packet from a client. Returns the new client state"
[client client-agent packet]
(println packet)
(handler client client-agent packet))
(defn process-serverbound-packet [client client-agent frame]
(let [packet (decode-packet! frame (req! client ::state/connection-state))]
(handle-packet client client-agent packet)))
(defn process-incoming-frame! [client-agent frame]
(send client-agent process-serverbound-packet client-agent frame))