Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d41eaaf56 | |||
| 5fffaf2582 | |||
| 1f3173a563 | |||
| e1a5c6528d | |||
| cfb4518a53 | |||
| 24d20ae01f | |||
| 4ed4eb46b4 | |||
| ba1889d40d | |||
| d82e05505c | |||
| 3e8bedb0c9 | |||
| 76149f0a69 | |||
| 15bfe7712b | |||
| 11339ef6ae | |||
| a1b54b6172 | |||
| c649933afa | |||
| cb9c0fd68e | |||
| ba3321fb4f |
Generated
+50
-118
@@ -10,9 +10,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -31,14 +31,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.11"
|
version = "0.16.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"windows-sys",
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -81,14 +80,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
@@ -120,9 +119,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -138,21 +137,21 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.13.0"
|
version = "2.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.46.3"
|
version = "1.47.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
|
checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -162,9 +161,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
@@ -174,9 +173,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -207,9 +206,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_enum"
|
name = "num_enum"
|
||||||
version = "0.7.5"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num_enum_derive",
|
"num_enum_derive",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
@@ -217,9 +216,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_enum_derive"
|
name = "num_enum_derive"
|
||||||
version = "0.7.5"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -229,9 +228,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
@@ -286,7 +285,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -303,9 +302,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@@ -368,15 +367,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.26.0"
|
version = "3.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -401,18 +400,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "1.0.0+spec-1.1.0"
|
version = "1.1.1+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.25.4+spec-1.1.0"
|
version = "0.25.11+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
|
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@@ -422,9 +421,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.9+spec-1.1.0"
|
version = "1.1.2+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -443,11 +442,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.2+wasi-0.2.9"
|
version = "1.0.3+wasi-0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen 0.57.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -456,7 +455,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen 0.51.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -499,15 +498,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -517,75 +507,11 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_gnullvm",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.15"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -599,6 +525,12 @@ dependencies = [
|
|||||||
"wit-bindgen-rust-macro",
|
"wit-bindgen-rust-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.57.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-core"
|
name = "wit-bindgen-core"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
@@ -686,7 +618,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zztff"
|
name = "zztff"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"codepage-437",
|
"codepage-437",
|
||||||
"insta",
|
"insta",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "zztff"
|
name = "zztff"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Library for reading/writing ZZT worlds and boards"
|
description = "Library for reading/writing ZZT worlds and boards"
|
||||||
|
|||||||
+12
-12
@@ -18,7 +18,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Element {
|
pub enum Element {
|
||||||
Empty = 0,
|
Empty = 0,
|
||||||
BoardEdge = 1,
|
Edge = 1,
|
||||||
Messenger = 2,
|
Messenger = 2,
|
||||||
Monitor = 3,
|
Monitor = 3,
|
||||||
Player = 4,
|
Player = 4,
|
||||||
@@ -42,15 +42,15 @@ pub enum Element {
|
|||||||
Normal = 22,
|
Normal = 22,
|
||||||
Breakable = 23,
|
Breakable = 23,
|
||||||
Boulder = 24,
|
Boulder = 24,
|
||||||
SliderNs = 25,
|
SliderNS = 25,
|
||||||
SliderEw = 26,
|
SliderEW = 26,
|
||||||
Fake = 27,
|
Fake = 27,
|
||||||
Invisible = 28,
|
Invisible = 28,
|
||||||
BlinkWall = 29,
|
BlinkWall = 29,
|
||||||
Transporter = 30,
|
Transporter = 30,
|
||||||
Line = 31,
|
Line = 31,
|
||||||
Ricochet = 32,
|
Ricochet = 32,
|
||||||
BlinkRayH = 33,
|
BlinkEW = 33,
|
||||||
Bear = 34,
|
Bear = 34,
|
||||||
Ruffian = 35,
|
Ruffian = 35,
|
||||||
Object = 36,
|
Object = 36,
|
||||||
@@ -60,15 +60,15 @@ pub enum Element {
|
|||||||
Pusher = 40,
|
Pusher = 40,
|
||||||
Lion = 41,
|
Lion = 41,
|
||||||
Tiger = 42,
|
Tiger = 42,
|
||||||
BlinkRayV = 43,
|
BlinkNS = 43,
|
||||||
Head = 44,
|
Head = 44,
|
||||||
Segment = 45,
|
Segment = 45,
|
||||||
// 46 is unused
|
// 46 is unused
|
||||||
TextBlue = 47,
|
BlueText = 47,
|
||||||
TextGreen = 48,
|
GreenText = 48,
|
||||||
TextCyan = 49,
|
CyanText = 49,
|
||||||
TextRed = 50,
|
RedText = 50,
|
||||||
TextPurple = 51,
|
PurpleText = 51,
|
||||||
TextBrown = 52,
|
YellowText = 52,
|
||||||
TextBlack = 53,
|
WhiteText = 53,
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-6
@@ -1,9 +1,88 @@
|
|||||||
|
//! # Tools for reading/writing ZZT's file formats
|
||||||
|
//!
|
||||||
|
//! zztff is a library for working with [ZZT](https://en.wikipedia.org/wiki/ZZT) world and board files.
|
||||||
|
//! It is somewhat low-level; it allows control over every data field used by ZZT,
|
||||||
|
//! but creating new files requires some familiarity with the file format.
|
||||||
|
//!
|
||||||
|
//! zztff currently only supports the format used by ZZT 3.2, the last official release.
|
||||||
|
//! Super ZZT (the sequel) is not currently supported.
|
||||||
|
//!
|
||||||
|
//! ## String encoding
|
||||||
|
//!
|
||||||
|
//! zztff decodes all text data from extended ASCII into Unicode `String`s, because those are usually more convenient
|
||||||
|
//! for examining text and performing string manipuation. "Extended ASCII" is ambiguous as an encoding, so zztff assumes
|
||||||
|
//! [CP437](https://en.wikipedia.org/wiki/Code_page_437) for all conversions.
|
||||||
|
//! This is the de facto standard for most ZZT worlds and is what you want most of the time.
|
||||||
|
//!
|
||||||
|
//! If you have text that is not CP437, zztff will still read and write it just fine, and it is guaranteed to round-trip
|
||||||
|
//! back to disk unchanged. However, depending on the author's original encoding, foreign characters and dingbats
|
||||||
|
//! may display incorrectly.
|
||||||
|
//! (This is a difficult problem to solve in general, because ZZT formats do not specify their encodings, and if the
|
||||||
|
//! ZZT world was using a custom font, it can have characters without any Unicode equivalent.)
|
||||||
|
//!
|
||||||
|
//! Serialization will fail with [`EncodeError::EncodingError`] if you add a character to a String that doesn't have a
|
||||||
|
//! CP437 equivalent.
|
||||||
|
//! Serialization can also fail with [`EncodeError::StringTooLong`] if a String would exceed the maximum length of the
|
||||||
|
//! given text field in the file format.
|
||||||
|
//! (You can check the encoded length of a String by counting its codepoints, e.g., `s.chars().count()`;
|
||||||
|
//! zztff guarantees a 1:1 correspondence between codepoints in the String and bytes in the file format.)
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ### Reading an existing .ZZT file
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! let bytes = std::fs::read("TOWN.ZZT")?;
|
||||||
|
//! let world = zztff::World::from_bytes(&bytes)?;
|
||||||
|
//! let first3: Vec<&str> = world.boards.iter().take(3).map(|b| b.title.as_str()).collect();
|
||||||
|
//! println!("Loaded world {:?}, {} boards.", world.name, world.boards.len());
|
||||||
|
//! println!("First 3 boards: {:?}", first3);
|
||||||
|
//! # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Assuming you have TOWN.ZZT, this loads the file and prints a brief summary:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! Loaded world "TOWN", 34 boards.
|
||||||
|
//! First 3 boards: ["Introduction Screen", "Room One", "Armory"]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Creating a new .ZZT file
|
||||||
|
//!
|
||||||
|
//! Creating a valid world file from scratch is more involved.
|
||||||
|
//! Worlds must contain at least one board, and we must populate that board with a player.
|
||||||
|
//! And to create a functioning player, we must create a corresponding stat to allow the player tile to move around.
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use zztff::{World, Board, Tile, Stat, Element};
|
||||||
|
//!
|
||||||
|
//! let mut world = World::default();
|
||||||
|
//! world.name = "HELLO".into(); // should match stem of filename
|
||||||
|
//!
|
||||||
|
//! let mut board = Board::default();
|
||||||
|
//! board.set_tile(30, 12, Tile { // place a player tile in the center, at (30, 12)
|
||||||
|
//! element: Element::Player as u8,
|
||||||
|
//! color: 0x1f,
|
||||||
|
//! });
|
||||||
|
//! board.stats.push(Stat {
|
||||||
|
//! x: 30, // associate a stat with the player tile at (30, 12)
|
||||||
|
//! y: 12,
|
||||||
|
//! cycle: 1,
|
||||||
|
//! ..Stat::default()
|
||||||
|
//! });
|
||||||
|
//! world.boards.push(board);
|
||||||
|
//!
|
||||||
|
//! let bytes = world.to_bytes()?;
|
||||||
|
//! std::fs::write("HELLO.ZZT", &bytes)?;
|
||||||
|
//! # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod elements;
|
mod elements;
|
||||||
mod encoding;
|
mod errors;
|
||||||
mod error;
|
mod text;
|
||||||
mod parse;
|
mod world;
|
||||||
|
|
||||||
pub use elements::Element;
|
pub use elements::Element;
|
||||||
pub use encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
|
pub use errors::{DecodeError, EncodeError};
|
||||||
pub use error::{DecodeError, EncodeError};
|
pub use text::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
|
||||||
pub use parse::{Board, Program, Stat, Tile, World};
|
pub use world::{Board, Keys, Program, Stat, Tile, World};
|
||||||
|
|||||||
+45
-45
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
source: src/parse.rs
|
source: src/world.rs
|
||||||
expression: world
|
expression: world
|
||||||
---
|
---
|
||||||
World {
|
World {
|
||||||
@@ -38,7 +38,7 @@ World {
|
|||||||
saved_game: true,
|
saved_game: true,
|
||||||
boards: [
|
boards: [
|
||||||
Board {
|
Board {
|
||||||
name: "Title screen",
|
title: "Title screen",
|
||||||
tiles: [Tile; 1500],
|
tiles: [Tile; 1500],
|
||||||
max_shots: 255,
|
max_shots: 255,
|
||||||
is_dark: false,
|
is_dark: false,
|
||||||
@@ -46,7 +46,7 @@ World {
|
|||||||
exit_south: None,
|
exit_south: None,
|
||||||
exit_west: None,
|
exit_west: None,
|
||||||
exit_east: None,
|
exit_east: None,
|
||||||
restart_on_zap: false,
|
reenter_when_zapped: false,
|
||||||
message: "",
|
message: "",
|
||||||
enter_x: 1,
|
enter_x: 1,
|
||||||
enter_y: 1,
|
enter_y: 1,
|
||||||
@@ -55,8 +55,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -75,7 +75,7 @@ World {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Board {
|
Board {
|
||||||
name: "First board (NW, defaults)",
|
title: "First board (NW, defaults)",
|
||||||
tiles: [Tile; 1500],
|
tiles: [Tile; 1500],
|
||||||
max_shots: 255,
|
max_shots: 255,
|
||||||
is_dark: false,
|
is_dark: false,
|
||||||
@@ -87,7 +87,7 @@ World {
|
|||||||
exit_east: Some(
|
exit_east: Some(
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
restart_on_zap: false,
|
reenter_when_zapped: false,
|
||||||
message: "",
|
message: "",
|
||||||
enter_x: 1,
|
enter_x: 1,
|
||||||
enter_y: 1,
|
enter_y: 1,
|
||||||
@@ -96,8 +96,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -116,7 +116,7 @@ World {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Board {
|
Board {
|
||||||
name: "Second board (NE, non-default settings)",
|
title: "Second board (NE, non-default settings)",
|
||||||
tiles: [Tile; 1500],
|
tiles: [Tile; 1500],
|
||||||
max_shots: 0,
|
max_shots: 0,
|
||||||
is_dark: true,
|
is_dark: true,
|
||||||
@@ -128,7 +128,7 @@ World {
|
|||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
exit_east: None,
|
exit_east: None,
|
||||||
restart_on_zap: true,
|
reenter_when_zapped: true,
|
||||||
message: "Hello, board message!",
|
message: "Hello, board message!",
|
||||||
enter_x: 12,
|
enter_x: 12,
|
||||||
enter_y: 34,
|
enter_y: 34,
|
||||||
@@ -137,8 +137,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -157,7 +157,7 @@ World {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Board {
|
Board {
|
||||||
name: "Third board (SE, mostly object stats)",
|
title: "Third board (SE, mostly object stats)",
|
||||||
tiles: [Tile; 1500],
|
tiles: [Tile; 1500],
|
||||||
max_shots: 255,
|
max_shots: 255,
|
||||||
is_dark: false,
|
is_dark: false,
|
||||||
@@ -169,7 +169,7 @@ World {
|
|||||||
4,
|
4,
|
||||||
),
|
),
|
||||||
exit_east: None,
|
exit_east: None,
|
||||||
restart_on_zap: false,
|
reenter_when_zapped: false,
|
||||||
message: "",
|
message: "",
|
||||||
enter_x: 1,
|
enter_x: 1,
|
||||||
enter_y: 1,
|
enter_y: 1,
|
||||||
@@ -178,8 +178,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -198,8 +198,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 3,
|
x: 3,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 3,
|
cycle: 3,
|
||||||
p1: 51,
|
p1: 51,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -218,8 +218,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 20,
|
x: 20,
|
||||||
y: 4,
|
y: 4,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 0,
|
cycle: 0,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -238,8 +238,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 5,
|
x: 5,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 49,
|
p1: 49,
|
||||||
p2: 1,
|
p2: 1,
|
||||||
@@ -258,8 +258,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 7,
|
x: 7,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 3,
|
cycle: 3,
|
||||||
p1: 64,
|
p1: 64,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -278,8 +278,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 10,
|
x: 10,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 3,
|
cycle: 3,
|
||||||
p1: 9,
|
p1: 9,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -298,7 +298,7 @@ World {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
Board {
|
Board {
|
||||||
name: "Fourth board (SW, other stats)",
|
title: "Fourth board (SW, other stats)",
|
||||||
tiles: [Tile; 1500],
|
tiles: [Tile; 1500],
|
||||||
max_shots: 255,
|
max_shots: 255,
|
||||||
is_dark: false,
|
is_dark: false,
|
||||||
@@ -310,7 +310,7 @@ World {
|
|||||||
exit_east: Some(
|
exit_east: Some(
|
||||||
3,
|
3,
|
||||||
),
|
),
|
||||||
restart_on_zap: false,
|
reenter_when_zapped: false,
|
||||||
message: "",
|
message: "",
|
||||||
enter_x: 1,
|
enter_x: 1,
|
||||||
enter_y: 1,
|
enter_y: 1,
|
||||||
@@ -319,8 +319,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 1,
|
cycle: 1,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -339,8 +339,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 3,
|
x: 3,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: -1,
|
step_y: -1,
|
||||||
cycle: 4,
|
cycle: 4,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -359,8 +359,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 5,
|
x: 5,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 1,
|
step_y: 1,
|
||||||
cycle: 4,
|
cycle: 4,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -379,8 +379,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 7,
|
x: 7,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: 1,
|
step_x: 1,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 4,
|
cycle: 4,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -399,8 +399,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 9,
|
x: 9,
|
||||||
y: 2,
|
y: 2,
|
||||||
x_step: -1,
|
step_x: -1,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 4,
|
cycle: 4,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -419,8 +419,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 3,
|
x: 3,
|
||||||
y: 4,
|
y: 4,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 2,
|
cycle: 2,
|
||||||
p1: 4,
|
p1: 4,
|
||||||
p2: 4,
|
p2: 4,
|
||||||
@@ -439,8 +439,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 4,
|
x: 4,
|
||||||
y: 4,
|
y: 4,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 2,
|
cycle: 2,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -459,8 +459,8 @@ World {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 5,
|
x: 5,
|
||||||
y: 4,
|
y: 4,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 2,
|
cycle: 2,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: src/parse.rs
|
source: src/world.rs
|
||||||
|
assertion_line: 620
|
||||||
expression: all_tiles
|
expression: all_tiles
|
||||||
---
|
---
|
||||||
// board 0: Title screen
|
// board 0: Title screen
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use codepage_437::CP437_WINGDINGS;
|
use codepage_437::CP437_WINGDINGS;
|
||||||
|
|
||||||
use super::error::EncodeError;
|
use super::errors::EncodeError;
|
||||||
|
|
||||||
/// Serialize a multi-line code string for an object or a scroll.
|
/// Serialize a multi-line code string for an object or a scroll.
|
||||||
/// Uses ZZT's convention of CR-terminated lines.
|
///
|
||||||
|
/// Characters that do not have a CP437 equivalent will result in an [`EncodeError::EncodingError`].
|
||||||
pub fn encode_multiline(input: &str) -> Result<Vec<u8>, EncodeError> {
|
pub fn encode_multiline(input: &str) -> Result<Vec<u8>, EncodeError> {
|
||||||
input
|
input
|
||||||
.chars()
|
.chars()
|
||||||
@@ -33,8 +34,11 @@ pub fn decode_multiline(input: &[u8]) -> String {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize a single-line string for a board title.
|
/// Serialize a single-line string, such as a board title.
|
||||||
/// Newlines cannot be encoded because ZZT interprets them as dingbats (♪).
|
///
|
||||||
|
/// Newlines cannot be encoded because in single-line contexts, ZZT interprets that byte as a
|
||||||
|
/// dingbat, specifically the '♪' character. Passing a newline, or any non-CP437 character, will
|
||||||
|
/// result in an [`EncodeError::EncodingError`].
|
||||||
pub fn encode_oneline(input: &str) -> Result<Vec<u8>, EncodeError> {
|
pub fn encode_oneline(input: &str) -> Result<Vec<u8>, EncodeError> {
|
||||||
input
|
input
|
||||||
.chars()
|
.chars()
|
||||||
@@ -46,7 +50,7 @@ pub fn encode_oneline(input: &str) -> Result<Vec<u8>, EncodeError> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize a board title.
|
/// Deserialize a single-line string, such as a board title.
|
||||||
pub fn decode_oneline(input: &[u8]) -> String {
|
pub fn decode_oneline(input: &[u8]) -> String {
|
||||||
input.iter().map(|&x| CP437_WINGDINGS.decode(x)).collect()
|
input.iter().map(|&x| CP437_WINGDINGS.decode(x)).collect()
|
||||||
}
|
}
|
||||||
@@ -60,7 +64,7 @@ mod tests {
|
|||||||
encode_multiline(input).expect("Error in test")
|
encode_multiline(input).expect("Error in test")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_title(input: &str) -> Vec<u8> {
|
fn serialize_line(input: &str) -> Vec<u8> {
|
||||||
encode_oneline(input).expect("Error in test")
|
encode_oneline(input).expect("Error in test")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +77,16 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_oneline() {
|
fn roundtrip_oneline() {
|
||||||
let bytes: Vec<u8> = (0..=255).collect();
|
let bytes: Vec<u8> = (0..=255).collect();
|
||||||
assert_eq!(bytes, serialize_title(&decode_oneline(&bytes)))
|
assert_eq!(bytes, serialize_line(&decode_oneline(&bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_codepoint_per_byte() {
|
||||||
|
// This check guarantees that length checks on Unicode Strings will tell you something useful
|
||||||
|
// about how much headroom your text will have once serialized into a field in the file format.
|
||||||
|
let bytes: Vec<u8> = (0..=255).collect();
|
||||||
|
assert_eq!(256, decode_oneline(&bytes).chars().count());
|
||||||
|
assert_eq!(256, decode_multiline(&bytes).chars().count());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
+249
-44
@@ -8,26 +8,82 @@ use nom::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::elements::Element;
|
use super::elements::Element;
|
||||||
use super::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
|
use super::errors::{DecodeError, EncodeError};
|
||||||
use super::error::{DecodeError, EncodeError};
|
use super::text::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
|
||||||
|
|
||||||
/// A ZZT world file.
|
/// A data structure representing a ZZT world.
|
||||||
|
///
|
||||||
|
/// ZZT uses the same format for both worlds proper (.ZZT files) as well as their saved games (.SAV); saved games are
|
||||||
|
/// just snapshots of the world state after it's been played in. This `World` struct is for working with either type of
|
||||||
|
/// file.
|
||||||
|
///
|
||||||
|
/// Some of the fields are more esoteric than others due to this need of being able to represent the entire world state,
|
||||||
|
/// not just the starting conditions. If you just want to create a new world, it's safe to call `::default()` and leave
|
||||||
|
/// most of the fields alone. The only one that needs a non-default value is [`World::name`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
|
/// Value of the `ammo` counter.
|
||||||
pub ammo: i16,
|
pub ammo: i16,
|
||||||
|
/// Value of the `gems` counter.
|
||||||
pub gems: i16,
|
pub gems: i16,
|
||||||
|
/// Which keys the player is carrying.
|
||||||
pub keys: Keys,
|
pub keys: Keys,
|
||||||
|
/// Value of the `health` counter.
|
||||||
pub health: i16,
|
pub health: i16,
|
||||||
|
/// Index of the board the player starts on.
|
||||||
|
///
|
||||||
|
/// If this is a saved game, this field holds the index of the board that the player is _currently_ on, which is
|
||||||
|
/// where they will start play upon restoring their save.
|
||||||
pub starting_board: i16,
|
pub starting_board: i16,
|
||||||
|
/// Value of the `torches` counter.
|
||||||
pub torches: i16,
|
pub torches: i16,
|
||||||
|
/// Remaining cycles of torch light (0 = torch not active).
|
||||||
pub torch_cycles: i16,
|
pub torch_cycles: i16,
|
||||||
|
/// Remaining cycles of energizer effect (0 = not active).
|
||||||
pub energizer_cycles: i16,
|
pub energizer_cycles: i16,
|
||||||
|
/// Value of the `score` counter.
|
||||||
pub score: i16,
|
pub score: i16,
|
||||||
|
/// Stem of the world's filename, e.g., "TOWN" for a world named TOWN.ZZT (max 20 characters).
|
||||||
|
///
|
||||||
|
/// This field should typically match the filename of the .ZZT file you're writing to disk. If it differs, ZZT may
|
||||||
|
/// have trouble loading your file.
|
||||||
|
///
|
||||||
|
/// For saved games (.SAV files), this field should match the filename of the .ZZT file that was being played.
|
||||||
|
/// played. ZZT uses this field to associate the saved game with the world file it came from.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Named flags set by ZZT-OOP `#set` commands (max 20 characters each).
|
||||||
|
///
|
||||||
|
/// Empty strings represent unused slots. The slot order is mostly invisible to ZZT-OOP, and ZZT will fill up the
|
||||||
|
/// slots from lowest to highest. However, order does matter once the array is full: trying to `#set` an eleventh
|
||||||
|
/// flag overwrites the tenth slot.
|
||||||
|
///
|
||||||
|
/// The file format supports arbitrary names, but ZZT-OOP only recognizes uppercase flags. Additionally, ZZT-OOP's
|
||||||
|
/// quirky parsing rules mean that many special characters are not recognized; see [this wiki
|
||||||
|
/// article](https://wiki.zzt.org/wiki/Set) for details.
|
||||||
pub flags: [String; 10],
|
pub flags: [String; 10],
|
||||||
|
/// Value of the `time` counter.
|
||||||
pub time: i16,
|
pub time: i16,
|
||||||
|
/// Sub-second state for the `time` countdown.
|
||||||
|
///
|
||||||
|
/// When on a board with a time limit, ZZT decrements the `time` counter approximately every second. This field
|
||||||
|
/// tracks partial seconds that have elapsed, so that ZZT can decide between decrementing `time` during this tick
|
||||||
|
/// versus waiting another tick.
|
||||||
pub time_ticks: i16,
|
pub time_ticks: i16,
|
||||||
|
/// Whether this file is a saved game (.SAV) rather than a world file (.ZZT).
|
||||||
|
///
|
||||||
|
/// ZZT 3.2 uses this as an anti-cheat mechanism: the built-in editor refuses to open any files that have it set to
|
||||||
|
/// true. Otherwise, people could temporarily rename their save to have a .ZZT extension, then edit their way out of
|
||||||
|
/// a sticky situation.
|
||||||
|
///
|
||||||
|
/// (This was never very secure, and is even less of a deterrent nowadays. But it's still part of the file format.)
|
||||||
pub saved_game: bool,
|
pub saved_game: bool,
|
||||||
|
/// The boards in this world.
|
||||||
|
///
|
||||||
|
/// A valid world must have at least 1 board (the title screen). Trying to serialize an empty world will result in
|
||||||
|
/// an encoding error.
|
||||||
|
///
|
||||||
|
/// ZZT 3.2 supports a maximum of 101 boards per world. The file format (and zztff) can handle larger worlds, but
|
||||||
|
/// trying to load them in ZZT may cause it to crash.
|
||||||
pub boards: Vec<Board>,
|
pub boards: Vec<Board>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,29 +121,71 @@ pub struct Keys {
|
|||||||
pub white: bool,
|
pub white: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ZZT board.
|
/// An individual ZZT board.
|
||||||
|
///
|
||||||
|
/// Boards mainly exist within a containing [`World`]. But as part of a editing workflow, they can
|
||||||
|
/// also be packaged as standalone .BRD files, which use the same format; use
|
||||||
|
/// [`Board::from_bytes`] to parse one.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub name: String,
|
/// The board's title.
|
||||||
|
///
|
||||||
|
/// Board titles are not shown during gameplay; they exist for organizational purposes while
|
||||||
|
/// editing.
|
||||||
|
///
|
||||||
|
/// The file format supports a maximum length of 50 characters. But editors usually impose
|
||||||
|
/// shorter limits: ZZT's internal editor artificially limits titles to 34 characters, and
|
||||||
|
/// titles longer than 42 characters may cause visual glitches when displayed in a
|
||||||
|
/// standard-width message box.
|
||||||
|
pub title: String,
|
||||||
|
/// The 60x25 terrain grid, stored in order from left-to-right, top-to-bottom.
|
||||||
|
///
|
||||||
|
/// For convenience, [`Board::tile`] and [`Board::set_tile`] are available for coordinate-based
|
||||||
|
/// access, but in some cases if you are updating lots of tiles, it may be more efficient to
|
||||||
|
/// directly mutate this array.
|
||||||
pub tiles: [Tile; 1500],
|
pub tiles: [Tile; 1500],
|
||||||
|
/// Maximum number of bullets the player can have on screen at once on this board.
|
||||||
pub max_shots: u8,
|
pub max_shots: u8,
|
||||||
|
/// Whether the board is dark.
|
||||||
pub is_dark: bool,
|
pub is_dark: bool,
|
||||||
|
/// Board index of the northern neighbor, or `None` for no exit.
|
||||||
|
///
|
||||||
|
/// Internally, ZZT uses zero to indicate "no exit". For this reason, it is impossible for a
|
||||||
|
/// board edge to link to the title screen, because board index 0 is unrepresentable.
|
||||||
pub exit_north: Option<NonZero<u8>>,
|
pub exit_north: Option<NonZero<u8>>,
|
||||||
|
/// Board index of the southern neighbor, or `None` for no exit.
|
||||||
pub exit_south: Option<NonZero<u8>>,
|
pub exit_south: Option<NonZero<u8>>,
|
||||||
|
/// Board index of the western neighbor, or `None` for no exit.
|
||||||
pub exit_west: Option<NonZero<u8>>,
|
pub exit_west: Option<NonZero<u8>>,
|
||||||
|
/// Board index of the eastern neighbor, or `None` for no exit.
|
||||||
pub exit_east: Option<NonZero<u8>>,
|
pub exit_east: Option<NonZero<u8>>,
|
||||||
pub restart_on_zap: bool,
|
/// Whether the player, upon taking damage, is transported back to where they entered the board.
|
||||||
|
pub reenter_when_zapped: bool,
|
||||||
|
/// The currently-flashing status bar message (max 58 characters).
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
/// X coordinate where the player entered the board (1-based).
|
||||||
|
///
|
||||||
|
/// The entry coordinates are part of the "re-enter when zapped" feature. These don't matter
|
||||||
|
/// most of the time; ZZT ignores the coordinates from the file, preferring to update these with
|
||||||
|
/// the player's actual entry coordinates. The only time they are ever loaded is for the current
|
||||||
|
/// board when the user restores a save file.
|
||||||
pub enter_x: u8,
|
pub enter_x: u8,
|
||||||
|
/// Y coordinate where the player entered the board (1-based).
|
||||||
pub enter_y: u8,
|
pub enter_y: u8,
|
||||||
|
/// Time limit for the board in seconds (0 = no limit).
|
||||||
pub time_limit: i16,
|
pub time_limit: i16,
|
||||||
|
/// Status elements on this board (objects, creatures, the player, etc.).
|
||||||
|
///
|
||||||
|
/// ZZT 3.2 supports a maximum of 151 stats per board. The file format can handle more than
|
||||||
|
/// that, and zztff will happily read/write more than 151 stats, but ZZT may crash if this limit
|
||||||
|
/// is exceeded.
|
||||||
pub stats: Vec<Stat>,
|
pub stats: Vec<Stat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Board {
|
impl Default for Board {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Board {
|
Board {
|
||||||
name: String::new(),
|
title: String::new(),
|
||||||
tiles: [Tile::default(); 1500],
|
tiles: [Tile::default(); 1500],
|
||||||
max_shots: 255,
|
max_shots: 255,
|
||||||
is_dark: false,
|
is_dark: false,
|
||||||
@@ -95,8 +193,11 @@ impl Default for Board {
|
|||||||
exit_south: None,
|
exit_south: None,
|
||||||
exit_west: None,
|
exit_west: None,
|
||||||
exit_east: None,
|
exit_east: None,
|
||||||
restart_on_zap: false,
|
reenter_when_zapped: false,
|
||||||
message: String::new(),
|
message: String::new(),
|
||||||
|
// (1, 1) is an objectively better default for enter x/y than (0, 0). It is possible to
|
||||||
|
// get ZZT to consume this value by restoring a game, and in that case (0, 0) will take
|
||||||
|
// the player out of bounds, leading to a crash.
|
||||||
enter_x: 1,
|
enter_x: 1,
|
||||||
enter_y: 1,
|
enter_y: 1,
|
||||||
time_limit: 0,
|
time_limit: 0,
|
||||||
@@ -116,7 +217,7 @@ impl Default for Board {
|
|||||||
impl Debug for Board {
|
impl Debug for Board {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Board")
|
f.debug_struct("Board")
|
||||||
.field("name", &self.name)
|
.field("title", &self.title)
|
||||||
.field("tiles", &format_args!("[Tile; 1500]"))
|
.field("tiles", &format_args!("[Tile; 1500]"))
|
||||||
.field("max_shots", &self.max_shots)
|
.field("max_shots", &self.max_shots)
|
||||||
.field("is_dark", &self.is_dark)
|
.field("is_dark", &self.is_dark)
|
||||||
@@ -124,7 +225,7 @@ impl Debug for Board {
|
|||||||
.field("exit_south", &self.exit_south)
|
.field("exit_south", &self.exit_south)
|
||||||
.field("exit_west", &self.exit_west)
|
.field("exit_west", &self.exit_west)
|
||||||
.field("exit_east", &self.exit_east)
|
.field("exit_east", &self.exit_east)
|
||||||
.field("restart_on_zap", &self.restart_on_zap)
|
.field("reenter_when_zapped", &self.reenter_when_zapped)
|
||||||
.field("message", &self.message)
|
.field("message", &self.message)
|
||||||
.field("enter_x", &self.enter_x)
|
.field("enter_x", &self.enter_x)
|
||||||
.field("enter_y", &self.enter_y)
|
.field("enter_y", &self.enter_y)
|
||||||
@@ -135,20 +236,92 @@ impl Debug for Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A status element on a ZZT board.
|
/// A status element on a ZZT board.
|
||||||
|
///
|
||||||
|
/// Stats provide additional data for specific tiles on the board. The majority of tiles will be
|
||||||
|
/// static, such as most of the board terrain (walls, water, empties, etc). But many things that
|
||||||
|
/// move, or any tile that needs state or code, will have an associated stat.
|
||||||
|
///
|
||||||
|
/// A stat's x/y coordinates are what link them to their associated tiles. Typically this is a 1:1
|
||||||
|
/// relationship: if there is an object tile at (12, 34), the board's stat list will usually have
|
||||||
|
/// exactly one entry that points to (12, 34). However, there are advanced use cases for having
|
||||||
|
/// multiple stats (or no stats!) in specific situations. So zztff does not enforce a 1:1
|
||||||
|
/// relationship---that is the responsibility of editing software that wants to be user friendly.
|
||||||
|
///
|
||||||
|
/// ZZT has a wide variety of tile types, called elements. The precise meanings of some of the
|
||||||
|
/// fields (particularly `p1`, `p2`, and `p3`) will vary depending on which tile the stat is pointed
|
||||||
|
/// at. You may want to consult a reference source, such as [the wiki articles on
|
||||||
|
/// elements](https://wiki.zzt.org/wiki/Element), for more info.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Stat {
|
pub struct Stat {
|
||||||
|
/// X coordinate of the stat's tile (1-based).
|
||||||
pub x: u8,
|
pub x: u8,
|
||||||
|
/// Y coordinate of the stat's tile (1-based).
|
||||||
pub y: u8,
|
pub y: u8,
|
||||||
pub x_step: i16,
|
/// Horizontal step delta.
|
||||||
pub y_step: i16,
|
///
|
||||||
|
/// This is usually -1, 0, or 1. Paired with `y_step`, the step deltas are used to encode
|
||||||
|
/// direction or movement. For example, for transporters, this encodes which direction the
|
||||||
|
/// transporter is facing, whereas for pushers, it encodes the direction of movement.
|
||||||
|
///
|
||||||
|
/// Step values of (0, 0) or the four cardinal directions are the most common. Other step values
|
||||||
|
/// are possible and are useful for advanced techniques, but care must be taken to ensure that
|
||||||
|
/// they are safe; ZZT's bounds checking code assumes step distances will never exceed 1, so
|
||||||
|
/// steps larger than that may go off-board and cause memory corruption.
|
||||||
|
pub step_x: i16,
|
||||||
|
/// Vertical step delta.
|
||||||
|
pub step_y: i16,
|
||||||
|
/// How often this stat is updated, in game ticks.
|
||||||
|
///
|
||||||
|
/// Roughly speaking, stats update every 1/cycle game ticks. A cycle 1 object will run its code
|
||||||
|
/// on every tick, a cycle 2 object runs every other tick, etc.
|
||||||
pub cycle: i16,
|
pub cycle: i16,
|
||||||
|
/// Element-specific parameter 1.
|
||||||
pub p1: u8,
|
pub p1: u8,
|
||||||
|
/// Element-specific parameter 2.
|
||||||
pub p2: u8,
|
pub p2: u8,
|
||||||
|
/// Element-specific parameter 3.
|
||||||
pub p3: u8,
|
pub p3: u8,
|
||||||
|
/// Index of the following stat in a centipede chain, or -1 if none.
|
||||||
|
///
|
||||||
|
/// Leader and follower do not need to be explicitly set if you are creating a world from
|
||||||
|
/// scratch; if there are no existing links, ZZT will automatically link adjacent segments into
|
||||||
|
/// a chain when the board first loads. These pointers are used mainly for preserving the state
|
||||||
|
/// of saved games---but a world author could still use them if they needed a specific centipede
|
||||||
|
/// layout for some reason.
|
||||||
pub follower: i16,
|
pub follower: i16,
|
||||||
|
/// Index of the leading stat in a centipede chain, or -1 if none.
|
||||||
pub leader: i16,
|
pub leader: i16,
|
||||||
|
/// The tile that is underneath this stat.
|
||||||
|
///
|
||||||
|
/// This is often an empty tile, but it may be something else if the stat was placed on top of
|
||||||
|
/// another terrain element (such as a fake wall/floor). ZZT uses this field to fill in the gap
|
||||||
|
/// if this stat ever moves; tracking the `under` type allows stats to move over terrain without
|
||||||
|
/// erasing it.
|
||||||
|
///
|
||||||
|
/// Note that this field only holds a tile, i.e., not a stat. In general, ZZT assumes that a
|
||||||
|
/// given coordinate can only be occupied by one stat at a time. For example, bullets may pass
|
||||||
|
/// over terrain, but they cannot pass by each other; they destroy each other on collision.
|
||||||
|
///
|
||||||
|
/// (Stats can be manually made to occupy the same x/y coordinates, but although this technique
|
||||||
|
/// is known as "stat stacking", strictly speaking ZZT has no way to represent one stat being
|
||||||
|
/// above or below another. The combination of stats in this way does not behave like two
|
||||||
|
/// overlapping items.)
|
||||||
pub under: Tile,
|
pub under: Tile,
|
||||||
|
/// Current position in an object's ZZT-OOP program.
|
||||||
|
///
|
||||||
|
/// This is a byte offset in the file format, which corresponds to a character offset in the
|
||||||
|
/// `String` representation. 0 starts from the top of the program and is the typical default.
|
||||||
|
///
|
||||||
|
/// ZZT uses -1 to indicate that execution has halted; this value can be set from within ZZT
|
||||||
|
/// using the `#end` command, but it can also be set directly in the file format to keep an
|
||||||
|
/// object from running its code when the board is first loaded.
|
||||||
|
///
|
||||||
|
/// ZZT has no call stack, so this is the main piece of execution state.
|
||||||
pub instruction_pointer: i16,
|
pub instruction_pointer: i16,
|
||||||
|
/// The stat's ZZT-OOP program, if any.
|
||||||
|
///
|
||||||
|
/// If a stat doesn't have a program, this is set to the empty string. This field is only ever
|
||||||
|
/// used by scrolls and objects, so it's pretty common for this to be empty.
|
||||||
pub program: Program,
|
pub program: Program,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,8 +330,8 @@ impl Default for Stat {
|
|||||||
Stat {
|
Stat {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
x_step: 0,
|
step_x: 0,
|
||||||
y_step: 0,
|
step_y: 0,
|
||||||
cycle: 0,
|
cycle: 0,
|
||||||
p1: 0,
|
p1: 0,
|
||||||
p2: 0,
|
p2: 0,
|
||||||
@@ -182,15 +355,36 @@ pub struct Tile {
|
|||||||
pub color: u8,
|
pub color: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stat's ZZT-OOP program.
|
/// A stat's ZZT-OOP program text.
|
||||||
///
|
///
|
||||||
/// In ZZT, stats can either have their own code or bind to another stat's code.
|
/// Stats must either have their own program or bind to another stat's program. This is either-or:
|
||||||
/// This enum prevents invalid states where both are set.
|
/// in ZZT, it is impossible to represent a stat that has both (or neither). This enum models that
|
||||||
|
/// constraint.
|
||||||
|
///
|
||||||
|
/// Internally, this is because ZZT uses a single `i16` like an enum discriminant. Zero or more
|
||||||
|
/// indicates the length of the program text, i.e., how many bytes follow the stat on disk. Negative
|
||||||
|
/// values are interpreted as negated bind indexes: -123 means no program text follows, and that the
|
||||||
|
/// stat instead shares code with the 123rd stat on the board.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Program {
|
pub enum Program {
|
||||||
/// The stat owns its own code.
|
/// The stat has its own program code.
|
||||||
|
///
|
||||||
|
/// This is the typical default for most stats. For stats that don't have or need a program, an
|
||||||
|
/// empty `String` is used. The text has a maximum of 32767 characters, though in ZZT 3.2 you
|
||||||
|
/// will encounter other limits before that.
|
||||||
|
///
|
||||||
|
/// Despite the name, this has more to do with ZZT's behavior than with Rust ownership.
|
||||||
Own(String),
|
Own(String),
|
||||||
/// The stat is bound to another stat (index into the stats list).
|
/// The stat is bound to the code at the given stat index.
|
||||||
|
///
|
||||||
|
/// Bind indexes are 0-based, with the asterisk that zero is not representable due to how ZZT
|
||||||
|
/// encodes bind indexes. The smallest possible value is 1, which represents being bound to the
|
||||||
|
/// stat that comes immediately after the player.
|
||||||
|
///
|
||||||
|
/// The largest representable bind index is 32768. zztff will successfully serialize this value,
|
||||||
|
/// though it's only useful as a curiosity and will likely crash ZZT: the index will definitely
|
||||||
|
/// be out of bounds because it is impossible to serialize a stat list that long without hitting
|
||||||
|
/// other limits in the file format. Indexes larger than that will result in an [`EncodeError`].
|
||||||
Bound(NonZero<u16>),
|
Bound(NonZero<u16>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,13 +509,13 @@ impl Board {
|
|||||||
self.tiles[(y - 1) * 60 + (x - 1)] = tile;
|
self.tiles[(y - 1) * 60 + (x - 1)] = tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a board from bytes (including the 2-byte size header).
|
/// Parse a ZZT board from bytes.
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Board, DecodeError> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Board, DecodeError> {
|
||||||
// Ignore length bytes
|
// Ignore length bytes
|
||||||
let (input, _) = le_u16.parse(bytes)?;
|
let (input, _) = le_u16.parse(bytes)?;
|
||||||
|
|
||||||
// Read board name
|
// Read board title
|
||||||
let (input, name) = pstring(50)(input)?;
|
let (input, title) = pstring(50)(input)?;
|
||||||
|
|
||||||
// Read terrain
|
// Read terrain
|
||||||
const NUM_TILES: usize = 60 * 25;
|
const NUM_TILES: usize = 60 * 25;
|
||||||
@@ -349,7 +543,7 @@ impl Board {
|
|||||||
let exit_south = NonZero::new(exit_s);
|
let exit_south = NonZero::new(exit_s);
|
||||||
let exit_west = NonZero::new(exit_w);
|
let exit_west = NonZero::new(exit_w);
|
||||||
let exit_east = NonZero::new(exit_e);
|
let exit_east = NonZero::new(exit_e);
|
||||||
let (input, (restart_on_zap, message)) = (bool_u8, pstring(58)).parse(input)?;
|
let (input, (reenter_when_zapped, message)) = (bool_u8, pstring(58)).parse(input)?;
|
||||||
let (input, (enter_x, enter_y, time_limit)) = (le_u8, le_u8, le_i16).parse(input)?;
|
let (input, (enter_x, enter_y, time_limit)) = (le_u8, le_u8, le_i16).parse(input)?;
|
||||||
let (input, _) = take(16usize)(input)?;
|
let (input, _) = take(16usize)(input)?;
|
||||||
|
|
||||||
@@ -362,7 +556,7 @@ impl Board {
|
|||||||
let (_input, stats) = count(Stat::parse, num_stats as usize).parse(input)?;
|
let (_input, stats) = count(Stat::parse, num_stats as usize).parse(input)?;
|
||||||
|
|
||||||
Ok(Board {
|
Ok(Board {
|
||||||
name,
|
title,
|
||||||
tiles,
|
tiles,
|
||||||
max_shots,
|
max_shots,
|
||||||
is_dark,
|
is_dark,
|
||||||
@@ -370,7 +564,7 @@ impl Board {
|
|||||||
exit_south,
|
exit_south,
|
||||||
exit_east,
|
exit_east,
|
||||||
exit_west,
|
exit_west,
|
||||||
restart_on_zap,
|
reenter_when_zapped,
|
||||||
message,
|
message,
|
||||||
enter_x,
|
enter_x,
|
||||||
enter_y,
|
enter_y,
|
||||||
@@ -379,16 +573,16 @@ impl Board {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a standalone .brd file (same format, no world header).
|
|
||||||
pub fn from_brd_bytes(bytes: &[u8]) -> Result<Board, DecodeError> {
|
|
||||||
Board::from_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize this board to bytes.
|
/// Serialize this board to bytes.
|
||||||
|
///
|
||||||
|
/// Returns [`EncodeError::BoardTooLarge`] if the board is over 64 KiB when serialized. The file
|
||||||
|
/// format allows up to this size, but be aware that boards larger than 20 kB will cause memory
|
||||||
|
/// corruption in ZZT 3.2. Tools may want to warn the user if this function returns more than
|
||||||
|
/// 20,002 bytes (2 byte size header + 20 kB content).
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
result.push_padding(2); // reserve space for board size
|
result.push_padding(2); // reserve space for board size
|
||||||
result.push_string(50, &self.name)?;
|
result.push_string(50, &self.title)?;
|
||||||
|
|
||||||
// Encode terrain
|
// Encode terrain
|
||||||
let mut iter = self.tiles.iter().peekable();
|
let mut iter = self.tiles.iter().peekable();
|
||||||
@@ -410,7 +604,7 @@ impl Board {
|
|||||||
result.push(self.exit_south.map_or(0, |n| n.get()));
|
result.push(self.exit_south.map_or(0, |n| n.get()));
|
||||||
result.push(self.exit_west.map_or(0, |n| n.get()));
|
result.push(self.exit_west.map_or(0, |n| n.get()));
|
||||||
result.push(self.exit_east.map_or(0, |n| n.get()));
|
result.push(self.exit_east.map_or(0, |n| n.get()));
|
||||||
result.push_bool(self.restart_on_zap);
|
result.push_bool(self.reenter_when_zapped);
|
||||||
result.push_string(58, &self.message)?;
|
result.push_string(58, &self.message)?;
|
||||||
result.push(self.enter_x);
|
result.push(self.enter_x);
|
||||||
result.push(self.enter_y);
|
result.push(self.enter_y);
|
||||||
@@ -436,7 +630,7 @@ impl Board {
|
|||||||
|
|
||||||
impl Stat {
|
impl Stat {
|
||||||
fn parse(input: &[u8]) -> IResult<&[u8], Self, DecodeError> {
|
fn parse(input: &[u8]) -> IResult<&[u8], Self, DecodeError> {
|
||||||
let (input, (x, y, x_step, y_step)) = (le_u8, le_u8, le_i16, le_i16).parse(input)?;
|
let (input, (x, y, step_x, step_y)) = (le_u8, le_u8, le_i16, le_i16).parse(input)?;
|
||||||
let (input, (cycle, p1, p2, p3)) = (le_i16, le_u8, le_u8, le_u8).parse(input)?;
|
let (input, (cycle, p1, p2, p3)) = (le_i16, le_u8, le_u8, le_u8).parse(input)?;
|
||||||
let (input, (follower, leader)) = (le_i16, le_i16).parse(input)?;
|
let (input, (follower, leader)) = (le_i16, le_i16).parse(input)?;
|
||||||
let (input, (under_element, under_color)) = (le_u8, le_u8).parse(input)?;
|
let (input, (under_element, under_color)) = (le_u8, le_u8).parse(input)?;
|
||||||
@@ -461,8 +655,8 @@ impl Stat {
|
|||||||
Stat {
|
Stat {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
x_step,
|
step_x,
|
||||||
y_step,
|
step_y,
|
||||||
follower,
|
follower,
|
||||||
leader,
|
leader,
|
||||||
cycle,
|
cycle,
|
||||||
@@ -483,8 +677,8 @@ impl Stat {
|
|||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
result.push(self.x);
|
result.push(self.x);
|
||||||
result.push(self.y);
|
result.push(self.y);
|
||||||
result.push_i16(self.x_step);
|
result.push_i16(self.step_x);
|
||||||
result.push_i16(self.y_step);
|
result.push_i16(self.step_y);
|
||||||
result.push_i16(self.cycle);
|
result.push_i16(self.cycle);
|
||||||
result.push(self.p1);
|
result.push(self.p1);
|
||||||
result.push(self.p2);
|
result.push(self.p2);
|
||||||
@@ -574,22 +768,33 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use insta::assert_debug_snapshot;
|
use insta::assert_debug_snapshot;
|
||||||
|
|
||||||
const BYTES: &[u8] = include_bytes!("../tests/fixtures/all.zzt");
|
const WORLD: &[u8] = include_bytes!("../tests/fixtures/all.zzt");
|
||||||
|
const BOARD: &[u8] = include_bytes!("../tests/fixtures/title.brd");
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip() {
|
fn roundtrip() {
|
||||||
let world = World::from_bytes(BYTES).unwrap();
|
let world = World::from_bytes(WORLD).unwrap();
|
||||||
let encoded = world.to_bytes().unwrap();
|
let encoded = world.to_bytes().unwrap();
|
||||||
assert_eq!(BYTES, &encoded[..]);
|
assert_eq!(WORLD, &encoded[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_snapshot() {
|
fn decode_snapshot() {
|
||||||
let world = World::from_bytes(BYTES).unwrap();
|
let world = World::from_bytes(WORLD).unwrap();
|
||||||
assert_debug_snapshot!(world);
|
assert_debug_snapshot!(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format board terrain as two hex grids (elements, then colors).
|
#[test]
|
||||||
|
fn brd_roundtrip() {
|
||||||
|
// We don't bother with a snapshot test for the individual board case, because the world
|
||||||
|
// snapshot already exercises the contents of boards. This test is just to make sure it's
|
||||||
|
// *possible* to read/write standalone board files.
|
||||||
|
let board = Board::from_bytes(BOARD).unwrap();
|
||||||
|
let encoded = board.to_bytes().unwrap();
|
||||||
|
assert_eq!(BOARD, &encoded[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test helper: format board terrain as two hex grids (elements, then colors).
|
||||||
fn format_tiles(board: &Board) -> String {
|
fn format_tiles(board: &Board) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
for y in 1..=25 {
|
for y in 1..=25 {
|
||||||
@@ -611,10 +816,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tiles_snapshot() {
|
fn tiles_snapshot() {
|
||||||
let world = World::from_bytes(BYTES).unwrap();
|
let world = World::from_bytes(WORLD).unwrap();
|
||||||
let mut all_tiles = String::new();
|
let mut all_tiles = String::new();
|
||||||
for (i, board) in world.boards.iter().enumerate() {
|
for (i, board) in world.boards.iter().enumerate() {
|
||||||
all_tiles.push_str(&format!("// board {}: {}\n", i, board.name));
|
all_tiles.push_str(&format!("// board {}: {}\n", i, board.title));
|
||||||
all_tiles.push_str(&format_tiles(board));
|
all_tiles.push_str(&format_tiles(board));
|
||||||
}
|
}
|
||||||
insta::assert_snapshot!(all_tiles);
|
insta::assert_snapshot!(all_tiles);
|
||||||
Vendored
BIN
Binary file not shown.
Reference in New Issue
Block a user