handle.clj

Branches > main > 1e5053c > 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 "1.12.11"}]})
  (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/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)
                         :block-states {:bpe 0
                                        :palatte 1}
                         :biomes {:bpe 0
                                  :palatte 0}}
                  air {:block-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 zero :p
         ;; I think you can keep a registry of entity ids and stick this on the client
         :entity-id 0
         :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
         :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))