handle.clj
Branches > facts > 2405807 > 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.server.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 [client _]
{:facts [{::fact/id ::fact/server-list-requested
::client/client client}]})
(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 ::entity/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 ::entity/uuid)
client (assoc client
::state/connection-state ::state/play
::entity/id entity-id
::client/playing? true)
player {::entity/id entity-id
::entity/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 #{}}))
:keepalive? true}))
(defn handle-player-update-movement-packet [client {:keys [x feet-y z yaw pitch flags]}]
{:facts [{::fact/id ::fact/updated-player-movement
; TODO need the player here!
; It's never actually kept track of, I guess because you want to use the UUID
::position/x x
::position/y feet-y
::position/z z
::position/yaw yaw
::position/pitch pitch
; may be ::entity/ characteristics, idk
:pushing-against-wall? (:pushing-against-wall flags)
:on-ground? (:on-ground flags)}]})
(defmethod handle-packet ::set-player-rotation [client pk]
(handle-player-update-movement-packet client pk))
(defmethod handle-packet ::set-player-position-and-rotation [client pk]
(handle-player-update-movement-packet client pk))
(defmethod handle-packet ::set-player-position [client pk]
(handle-player-update-movement-packet client pk))
(defmethod handle-packet ::set-player-movement-flags [client pk]
(handle-player-update-movement-packet client pk))