handle.clj

Branches > main > e409cb4 > files / src / net / bowmap / protocol / handle.clj

(ns net.bowmap.protocol.handle
  (:require
   [net.bowmap.protocol.chunk :as chunk]
   [net.bowmap.protocol.client :as client]
   [net.bowmap.protocol.packet :as pk]
   [net.bowmap.protocol.packet.clientbound :as clientbound]
   [net.bowmap.protocol.packet.registry :as registry]
   [net.bowmap.protocol.packet.serverbound :as serverbound]
   [net.bowmap.protocol.registry :as reg]
   [net.bowmap.protocol.state :as state]
   [net.bowmap.protocol.entity :as entity]
   [net.bowmap.server.gamemode :as gamemode]
   [net.bowmap.server.player :as player]
   [net.bowmap.server.position :as position]
   [net.bowmap.server.server :as server]
   [net.bowmap.stormbow.fact :as fact]))

(defmulti handle-packet
  (fn [client packet]
    (::pk/id packet)))

(defmethod handle-packet :default [_ _]
  {})

(defmethod handle-packet ::serverbound/handshaking [client pk]
  (let [{:keys! [intent protocol]} pk]
    {:client (assoc client
                    ::state/connection-state intent
                    :protocol-id protocol)}))

(defmethod handle-packet ::serverbound/status-request [_ _]
  {:facts [{::fact/id ::fact/server-list-requested}]})

(defmethod handle-packet ::serverbound/ping-request [_ pk]
  {:packets [{::pk/id ::clientbound/pong-response
              :timestamp (:timestamp pk)}]})

(defmethod handle-packet ::serverbound/login-start [client pk]
  (let [{:keys! [uuid username]} pk]
    {:client (assoc client ::player/uuid uuid)
     :packets [{::pk/id ::clientbound/login-success
                :profile {:uuid uuid
                          :username username
                          :properties []}
                :session-id (random-uuid)}]}))

(defmethod handle-packet ::serverbound/login-acknowledged [client _]
  {:client (assoc client ::state/connection-state ::state/config)
   :packets [{::pk/id ::clientbound/known-packs
              ;; TODO also probably good to unbundle this
              :known-packs [{:namespace "minecraft"
                             :id "core"
                             :version "26.2"}]}]})

; TODO how to update state here?
; Yield facts from this as well and send 'em in!
; Or well, this can be retried so just use the dosync
(defmethod handle-packet ::serverbound/known-packs [client _]
  ; TODO move these tags to the registries namespace
  {:packets (conj (vec (registry/registry-packets))
                  {::pk/id ::clientbound/update-tags
                   :tagged-registries [{:registry "minecraft:block"
                                        :tags [{:tag-name "bowmap:infiniburn"
                                                :entries []}]}]}

                  {::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 []}]}]}

                  {::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 []}]}]}

                  {::pk/id ::clientbound/finish-config})})


; May want this "world" elsewhere, or have some programmatic code to generate it, or implement anvil and go from there?
(defn- build-chunk-packets []
  (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 handle-packet ::serverbound/finish-config [client _]
  ; TODO shit, this is not a pure function!
  ; I'm just going to pretend like this is a pure function for now.
  (let [entity-id (entity/next-entity-id!)
        uuid (req! client ::player/uuid)
        client (assoc client
                      ::state/connection-state ::state/play
                      ::entity/id entity-id
                      ::client/playing? true)
        ; TODO should players have entity ids?
        ; Not sure if that's a player-level concern
        ; Players have associated protocol entities, of course, so this may not be needed
        player {::entity/id entity-id
                ::player/uuid uuid}]
    {:client client
     :facts [{::fact/id ::fact/player-joined
              ::player/player player}]
     :packets (-> [{::pk/id ::clientbound/login
                    :entity-id entity-id
                    :hardcore? false
                    :dimension-names [reg/world-dimension]
                    :max-players server/max-player-count
                    :view-distance 32
                    :simulation-distance 32
                    :reduced-debug-info? false
                    :enable-respawn-screen? false
                    :do-limited-crafting? false
                    :dimension-type-id reg/world-dimension-id
                    :dimension-name reg/world-dimension
                    :hashed-seed 203894
                    :gamemode ::gamemode/adventure
                    :debug-world? false
                    :superflat-world? true
                    :has-death-location? false
                    :portal-cooldown 10
                    :sea-level 60
                    :online-mode? false
                    :enforce-secure-chat? true}
                   {::pk/id ::clientbound/game-event
                    :event :wait-for-chunks}]
                  (into (build-chunk-packets))
                  (conj {::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 #{}}))
     :keep-alive? true}))

(defn handle-player-update-movement-packet [{::player/keys! [uuid]}
                                            {:keys [& ::position/x ::position/y ::position/z ::position/yaw ::position/pitch]
                                             :as packet
                                             :select movement}]
  {:facts [(merge {::fact/id ::fact/updated-player-movement
                   ::player/uuid uuid}
                  ; may be ::entity/ characteristics, idk
                  movement (zipmap (:flags packet) (repeat true)))]})

(defmethod handle-packet ::serverbound/set-player-rotation [client pk]
  (handle-player-update-movement-packet client pk))

(defmethod handle-packet ::serverbound/set-player-position-and-rotation [client pk]
  (handle-player-update-movement-packet client pk))

(defmethod handle-packet ::serverbound/set-player-position [client pk]
  (handle-player-update-movement-packet client pk))

(defmethod handle-packet ::serverbound/set-player-movement-flags [client pk]
  (handle-player-update-movement-packet client pk))