(ns ozelot.packets
(:use [packet-definitions :only [packets]]
[clojure.java.io]))
(defn long-str [& x] (clojure.string/join "\n" x))
(def clientbound-file "./.clientbound-packets.generated.rs")
(def serverbound-file "./.serverbound-packets.generated.rs")
(def clientbound-enum-file "./.clientbound-enum.generated.rs")
(def serverbound-enum-file "./.serverbound-enum.generated.rs")
(def warning (long-str
"/* This file is automatically generated by packets.clj"
"Do not manually edit this file, if you wish to make"
"changes here, then edit and rerun packets.clj */\n\n"))
(spit clientbound-file warning)
(spit serverbound-file warning)
(spit clientbound-enum-file warning)
(spit serverbound-enum-file warning)
(def clientstates ["Handshake" "Status" "Login" "Play"])
(def clientbound-packets
(->> packets
:clientbound
(map (fn [[key value]]
(map (fn [packet]
(assoc packet :state key))
value)))
(apply concat)))
(def serverbound-packets
(->> packets
:serverbound
(map (fn [[key value]]
(map (fn [packet]
(assoc packet :state key))
value)))
(apply concat)))
(defn enum-definition [name packets]
(format
(long-str "/// Represents a single packet"
"#[derive(Debug, PartialEq, Clone)]"
"pub enum %s {"
(apply str
(for [{name :name} packets]
(format " %s(%s),\n" name name)
))
"}"
""
"")
name))
(spit clientbound-enum-file (enum-definition "ClientboundPacket" clientbound-packets) :append true)
(spit serverbound-enum-file (enum-definition "ServerboundPacket" serverbound-packets) :append true)
(defn enum-fn-deserialize-id [state packets]
(format
(long-str " &ClientState::%s => {"
(if (empty? packets)
" Err(\"No packet available in this state\".into())\n"
(long-str " match packet_id {"
(apply str
(for [{name :name id :id} packets]
(format " %s => Ok(%s::deserialize(r)?),\n" id name)))
" _ => bail!(\"No packet with id {} in state {}\", packet_id, state),"
" }"))
" },"
"")
state))
(defn enum-fn-deserialize-state [packets]
(long-str " match state {"
(apply str
(for [state clientstates]
(enum-fn-deserialize-id state (filter (fn [p] (= (p :state) state)) packets))))
" }"))
(defn enum-fn-deserialize [packets]
(long-str " fn deserialize<R: Read>(r: &mut R, state: &ClientState) -> Result<Self> {"
" let packet_id = read_varint(r)?;"
(enum-fn-deserialize-state packets)
" }"))
(defn enum-fn-get-packet-name [packets packet-type]
(long-str " fn get_packet_name(&self) -> &str {"
" match self {"
(apply str
(for [{name :name} packets]
(format " &%s::%s(..) => \"%s\",\n"
packet-type name name)))
" }"
" }"))
(defn enum-fn-get-state [packets packet-type]
(long-str " fn get_clientstate(&self) -> ClientState {"
" match self {"
(apply str
(for [{name :name state :state} packets]
(format " &%s::%s(..) => ClientState::%s,\n"
packet-type name state)))
" }"
" }"))
(defn enum-fn-get-id [packets packet-type]
(long-str " fn get_id(&self) -> i32 {"
" match self {"
(apply str
(for [{name :name id :id} packets]
(format " &%s::%s(..) => %s,\n"
packet-type name id)))
" }"
" }"))
(defn enum-fn-to-u8 [packets packet-type]
(long-str " fn to_u8(&self) -> Result<Vec<u8>> {"
" match self {"
(apply str
(for [{name :name} packets]
(format " &%s::%s(ref x) => x.to_u8(),\n"
packet-type name)))
" }"
" }"))
(defn enum-impl-packet [packet-type packets]
(format
(long-str "impl Packet for %s {"
(enum-fn-deserialize packets)
(enum-fn-get-packet-name packets packet-type)
(enum-fn-get-state packets packet-type)
(enum-fn-get-id packets packet-type)
(enum-fn-to-u8 packets packet-type)
"}"
"impl fmt::Display for %s {"
" fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {"
" write!(f, \"%s of type {}\", self.get_packet_name())"
" }"
"}")
packet-type
packet-type
packet-type))
(spit clientbound-enum-file (enum-impl-packet "ClientboundPacket" clientbound-packets) :append true)
(spit serverbound-enum-file (enum-impl-packet "ServerboundPacket" serverbound-packets) :append true)
(defn fields-type-str [fields]
(apply str
(map
(fn [{x :name y :type}]
(format " %s: %s,\n" x y))
fields)))
(defn fields-name-str [fields]
(apply str
(map
(fn [{name :name}]
(format " %s: %s,\n" name name))
fields)))
(defn fields-fn-definition-str [fields]
(apply (fn [& x] (clojure.string/join ", " x))
(map
(fn [{name :name type :type}]
(format "%s: %s" name type))
fields)))
(defn write-fields-str [fields]
(apply str
(map
(fn [{name :name type :type read :read}]
(format " write_%s(&self.%s, &mut ret)?;\n"
(if (nil? read)
type
read)
name))
fields)))
(defn fn-get-packet-id [{name :name id :id}]
(format
(long-str " fn get_packet_id() -> i32 {"
" %s"
" }")
id))
(defn read-fields-str [fields]
(apply str
(map
(fn [{name :name type :type read :read}]
(format " %s: read_%s(r)?,\n"
name
(if (nil? read)
type
read)))
fields)))
(defn fn-deserialize [packet packet-type]
(let [{name :name fields :fields automatic-serialize :automatic-serialize} packet]
(format
(long-str " fn deserialize<R: Read>(r: &mut R) -> Result<%sPacket> {"
" Ok(%sPacket::%s(%s {"
(read-fields-str fields)
" }))"
" }")
packet-type packet-type name name)))
(defn fn-to-u8 [{name :name fields :fields automatic-serialize :automatic-serialize}]
(if (nil? automatic-serialize)
(long-str " fn to_u8(&self) -> Result<Vec<u8>> {"
" let mut ret = Vec::new();"
(format " write_varint(&%s::get_packet_id(), &mut ret)?;" name)
(write-fields-str fields)
" Ok(ret)"
" }")
""))
(defn fn-new [{fields :fields name :name automatic-serialize :automatic-serialize} packet-type]
(format (long-str " pub fn %s(%s) -> %sPacket {"
" %sPacket::%s(%s {"
"%s })"
" }"
)
(if (nil? automatic-serialize) "new" "new_raw")
(fields-fn-definition-str fields)
packet-type
packet-type
name
name
(fields-name-str fields)))
(defn packet-getter-fns [{fields :fields}]
(apply str
(for [{name :name type :type getter-docs :getter} fields]
(if (nil? getter-docs)
""
(format
(long-str " /// %s"
" pub fn get_%s(&self) -> &%s {"
" &self.%s"
" }")
getter-docs name type name)))))
(defn packet-impl [packet packet-type]
(let [{name :name automatic-serialize :automatic-serialize} packet]
(long-str (format "impl %s {" name)
(fn-get-packet-id packet)
(when (nil? automatic-serialize) (fn-deserialize packet packet-type))
(fn-to-u8 packet)
(fn-new packet packet-type)
(packet-getter-fns packet)
"}"
""
"")))
(defn packet-definitions [packets packet-type]
(apply str
(for [packet packets]
(let [{name :name automatic-serialize :automatic-serialize fields :fields} packet
fields-str (fields-type-str fields)
struct-def (format "#[derive(Debug, PartialEq, Clone)]\npub struct %s {\n%s}\n\n" name fields-str)
impl-def (packet-impl packet packet-type)]
(str struct-def impl-def)
))))
(spit clientbound-file (packet-definitions clientbound-packets "Clientbound") :append true)
(spit serverbound-file (packet-definitions serverbound-packets "Serverbound") :append true)