mindustry logic execution, map- and schematic- parsing and rendering
conveyors (#3)
todo plastanium convs, ducts, conveyor interactions, bridges
bendn 2023-07-19
parent 45b0b22 · commit 559923d
-rw-r--r--.github/example.pngbin775 -> 5020 bytes
-rw-r--r--Cargo.toml6
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-0-1.pngbin277 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-0-2.pngbin283 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-0-3.pngbin278 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-0.png (renamed from assets/blocks/distribution/conveyors/armored-conveyor-0-0.png)bin277 -> 277 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-1-1.pngbin475 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-1-2.pngbin485 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-1-3.pngbin479 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-1.png (renamed from assets/blocks/distribution/conveyors/armored-conveyor-1-0.png)bin488 -> 488 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-2-1.pngbin292 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-2-2.pngbin294 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-2-3.pngbin299 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-2.png (renamed from assets/blocks/distribution/conveyors/armored-conveyor-2-0.png)bin300 -> 300 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-3-1.pngbin220 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-3-2.pngbin233 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-3-3.pngbin230 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-3.png (renamed from assets/blocks/distribution/conveyors/armored-conveyor-3-0.png)bin227 -> 227 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-4-1.pngbin290 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-4-2.pngbin289 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-4-3.pngbin292 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/armored-conveyor-4.png (renamed from assets/blocks/distribution/conveyors/armored-conveyor-4-0.png)bin296 -> 296 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-0-1.pngbin174 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-0-2.pngbin173 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-0-3.pngbin168 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-0.png (renamed from assets/blocks/distribution/conveyors/conveyor-0-0.png)bin175 -> 175 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-1-1.pngbin408 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-1-2.pngbin419 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-1-3.pngbin409 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-1.png (renamed from assets/blocks/distribution/conveyors/conveyor-1-0.png)bin375 -> 375 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-2-1.pngbin213 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-2-2.pngbin204 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-2-3.pngbin214 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-2.png (renamed from assets/blocks/distribution/conveyors/conveyor-2-0.png)bin226 -> 226 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-3-1.pngbin220 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-3-2.pngbin233 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-3-3.pngbin231 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-3.png (renamed from assets/blocks/distribution/conveyors/conveyor-3-0.png)bin227 -> 227 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-4-1.pngbin203 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-4-2.pngbin189 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-4-3.pngbin201 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/conveyor-4.png (renamed from assets/blocks/distribution/conveyors/conveyor-4-0.png)bin211 -> 211 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-0-1.pngbin182 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-0-2.pngbin182 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-0-3.pngbin177 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-0.png (renamed from assets/blocks/distribution/conveyors/titanium-conveyor-0-0.png)bin184 -> 184 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-1-1.pngbin441 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-1-2.pngbin454 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-1-3.pngbin448 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-1.png (renamed from assets/blocks/distribution/conveyors/titanium-conveyor-1-0.png)bin410 -> 410 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-2-1.pngbin220 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-2-2.pngbin212 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-2-3.pngbin223 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-2.png (renamed from assets/blocks/distribution/conveyors/titanium-conveyor-2-0.png)bin234 -> 234 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-3-1.pngbin218 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-3-2.pngbin231 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-3-3.pngbin228 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-3.png (renamed from assets/blocks/distribution/conveyors/titanium-conveyor-3-0.png)bin225 -> 225 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-4-1.pngbin210 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-4-2.pngbin198 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-4-3.pngbin211 -> 0 bytes
-rw-r--r--assets/blocks/distribution/conveyors/titanium-conveyor-4.png (renamed from assets/blocks/distribution/conveyors/titanium-conveyor-4-0.png)bin215 -> 215 bytes
-rw-r--r--build.rs11
-rw-r--r--src/access.rs1
-rw-r--r--src/block/distribution.rs355
-rw-r--r--src/block/drills.rs2
-rw-r--r--src/block/environment.rs2
-rw-r--r--src/block/liquid.rs56
-rw-r--r--src/block/logic.rs209
-rw-r--r--src/block/mod.rs158
-rw-r--r--src/block/payload.rs75
-rw-r--r--src/block/power.rs18
-rw-r--r--src/block/production.rs4
-rw-r--r--src/block/simple.rs26
-rw-r--r--src/block/turrets.rs2
-rw-r--r--src/block/walls.rs2
-rw-r--r--src/data/map.rs344
-rw-r--r--src/data/mod.rs87
-rw-r--r--src/data/renderer.rs365
-rw-r--r--src/data/schematic.rs990
-rw-r--r--src/exe/draw.rs4
-rw-r--r--src/exe/map.rs4
-rw-r--r--src/exe/mod.rs33
-rw-r--r--src/exe/print.rs64
-rw-r--r--src/item/storage.rs37
-rw-r--r--src/lib.rs2
-rw-r--r--src/team.rs14
-rw-r--r--src/utils/array.rs63
-rw-r--r--src/utils/image.rs71
-rw-r--r--src/utils/mod.rs1
-rw-r--r--tmp.png0
91 files changed, 1491 insertions, 1515 deletions
diff --git a/.github/example.png b/.github/example.png
index 26431d9..af8ce7d 100644
--- a/.github/example.png
+++ b/.github/example.png
Binary files differ
diff --git a/Cargo.toml b/Cargo.toml
index ca487ee..930972c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,11 +22,12 @@ const-str = "0.5.5"
color-hex = "0.2.0"
zip = { version = "0.6.6", features = ["zstd"], default-features = false }
tinyrand = "0.5.0"
-seq-macro = "0.3.4"
tinyrand-std = "0.5.0"
dashmap = "5.4.0"
fast_image_resize = "2.7.3"
thiserror = "1.0.41"
+bobbin-bits = "0.1.1"
+blurslice = "0.1.0"
[build-dependencies]
zip = { version = "0.6.6", features = ["zstd"], default-features = false }
@@ -41,3 +42,6 @@ debug = 2
opt-level = 3
lto = true
incremental = true
+
+[dev-dependencies]
+diff = "0.1"
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png
deleted file mode 100644
index 4e3d884..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-0-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png
deleted file mode 100644
index ca1ca5e..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-0-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png
deleted file mode 100644
index b5afa99..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-0-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-0-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-0.png
index 2995011..2995011 100644
--- a/assets/blocks/distribution/conveyors/armored-conveyor-0-0.png
+++ b/assets/blocks/distribution/conveyors/armored-conveyor-0.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png
deleted file mode 100644
index b951ec1..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-1-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png
deleted file mode 100644
index db58abe..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-1-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png
deleted file mode 100644
index 8e8ecae..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-1-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-1-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-1.png
index 08def6a..08def6a 100644
--- a/assets/blocks/distribution/conveyors/armored-conveyor-1-0.png
+++ b/assets/blocks/distribution/conveyors/armored-conveyor-1.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png
deleted file mode 100644
index c1c7395..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-2-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png
deleted file mode 100644
index 5401d76..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-2-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png
deleted file mode 100644
index feb39f4..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-2-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-2-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-2.png
index dde9319..dde9319 100644
--- a/assets/blocks/distribution/conveyors/armored-conveyor-2-0.png
+++ b/assets/blocks/distribution/conveyors/armored-conveyor-2.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png
deleted file mode 100644
index 722ba2d..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-3-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png
deleted file mode 100644
index 8f3b6a0..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-3-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png
deleted file mode 100644
index ec4e840..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-3-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-3-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-3.png
index a4e76f2..a4e76f2 100644
--- a/assets/blocks/distribution/conveyors/armored-conveyor-3-0.png
+++ b/assets/blocks/distribution/conveyors/armored-conveyor-3.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png
deleted file mode 100644
index 2d7f090..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-4-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png
deleted file mode 100644
index a1d8c76..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-4-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png b/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png
deleted file mode 100644
index e707dd8..0000000
--- a/assets/blocks/distribution/conveyors/armored-conveyor-4-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/armored-conveyor-4-0.png b/assets/blocks/distribution/conveyors/armored-conveyor-4.png
index 868b0f4..868b0f4 100644
--- a/assets/blocks/distribution/conveyors/armored-conveyor-4-0.png
+++ b/assets/blocks/distribution/conveyors/armored-conveyor-4.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-0-1.png b/assets/blocks/distribution/conveyors/conveyor-0-1.png
deleted file mode 100644
index 6f0b92f..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-0-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-0-2.png b/assets/blocks/distribution/conveyors/conveyor-0-2.png
deleted file mode 100644
index 2633e5d..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-0-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-0-3.png b/assets/blocks/distribution/conveyors/conveyor-0-3.png
deleted file mode 100644
index 4406e12..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-0-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-0-0.png b/assets/blocks/distribution/conveyors/conveyor-0.png
index 5f7264e..5f7264e 100644
--- a/assets/blocks/distribution/conveyors/conveyor-0-0.png
+++ b/assets/blocks/distribution/conveyors/conveyor-0.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-1-1.png b/assets/blocks/distribution/conveyors/conveyor-1-1.png
deleted file mode 100644
index 6dfe694..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-1-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-1-2.png b/assets/blocks/distribution/conveyors/conveyor-1-2.png
deleted file mode 100644
index ef313ad..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-1-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-1-3.png b/assets/blocks/distribution/conveyors/conveyor-1-3.png
deleted file mode 100644
index e5ccf27..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-1-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-1-0.png b/assets/blocks/distribution/conveyors/conveyor-1.png
index c50b281..c50b281 100644
--- a/assets/blocks/distribution/conveyors/conveyor-1-0.png
+++ b/assets/blocks/distribution/conveyors/conveyor-1.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-2-1.png b/assets/blocks/distribution/conveyors/conveyor-2-1.png
deleted file mode 100644
index 24f5127..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-2-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-2-2.png b/assets/blocks/distribution/conveyors/conveyor-2-2.png
deleted file mode 100644
index 8faeeaa..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-2-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-2-3.png b/assets/blocks/distribution/conveyors/conveyor-2-3.png
deleted file mode 100644
index 1fa6d17..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-2-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-2-0.png b/assets/blocks/distribution/conveyors/conveyor-2.png
index 9dc27e1..9dc27e1 100644
--- a/assets/blocks/distribution/conveyors/conveyor-2-0.png
+++ b/assets/blocks/distribution/conveyors/conveyor-2.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-3-1.png b/assets/blocks/distribution/conveyors/conveyor-3-1.png
deleted file mode 100644
index bd7f0b5..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-3-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-3-2.png b/assets/blocks/distribution/conveyors/conveyor-3-2.png
deleted file mode 100644
index b8bd971..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-3-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-3-3.png b/assets/blocks/distribution/conveyors/conveyor-3-3.png
deleted file mode 100644
index 4870394..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-3-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-3-0.png b/assets/blocks/distribution/conveyors/conveyor-3.png
index 0b92a66..0b92a66 100644
--- a/assets/blocks/distribution/conveyors/conveyor-3-0.png
+++ b/assets/blocks/distribution/conveyors/conveyor-3.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-4-1.png b/assets/blocks/distribution/conveyors/conveyor-4-1.png
deleted file mode 100644
index 74be7c8..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-4-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-4-2.png b/assets/blocks/distribution/conveyors/conveyor-4-2.png
deleted file mode 100644
index 92fb21e..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-4-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-4-3.png b/assets/blocks/distribution/conveyors/conveyor-4-3.png
deleted file mode 100644
index 9124688..0000000
--- a/assets/blocks/distribution/conveyors/conveyor-4-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/conveyor-4-0.png b/assets/blocks/distribution/conveyors/conveyor-4.png
index adf781a..adf781a 100644
--- a/assets/blocks/distribution/conveyors/conveyor-4-0.png
+++ b/assets/blocks/distribution/conveyors/conveyor-4.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png
deleted file mode 100644
index be18171..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png
deleted file mode 100644
index 020968c..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png
deleted file mode 100644
index 5756c57..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-0-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-0.png
index 7cd0f41..7cd0f41 100644
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-0-0.png
+++ b/assets/blocks/distribution/conveyors/titanium-conveyor-0.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png
deleted file mode 100644
index 89ad916..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png
deleted file mode 100644
index fa082af..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png
deleted file mode 100644
index fd22fd4..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-1-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-1.png
index 0f8b737..0f8b737 100644
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-1-0.png
+++ b/assets/blocks/distribution/conveyors/titanium-conveyor-1.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png
deleted file mode 100644
index a53a5c8..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png
deleted file mode 100644
index 758743d..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png
deleted file mode 100644
index 4da1385..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-2-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-2.png
index a0f2ef7..a0f2ef7 100644
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-2-0.png
+++ b/assets/blocks/distribution/conveyors/titanium-conveyor-2.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png
deleted file mode 100644
index cb75fba..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png
deleted file mode 100644
index 9a07028..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png
deleted file mode 100644
index 40d9b6e..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-3-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-3.png
index cdaa149..cdaa149 100644
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-3-0.png
+++ b/assets/blocks/distribution/conveyors/titanium-conveyor-3.png
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png
deleted file mode 100644
index 939dd83..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-1.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png
deleted file mode 100644
index 571fbd6..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-2.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png
deleted file mode 100644
index be9095b..0000000
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-3.png
+++ /dev/null
Binary files differ
diff --git a/assets/blocks/distribution/conveyors/titanium-conveyor-4-0.png b/assets/blocks/distribution/conveyors/titanium-conveyor-4.png
index b8fbdcf..b8fbdcf 100644
--- a/assets/blocks/distribution/conveyors/titanium-conveyor-4-0.png
+++ b/assets/blocks/distribution/conveyors/titanium-conveyor-4.png
Binary files differ
diff --git a/build.rs b/build.rs
index e9ce4ca..daa0ccd 100644
--- a/build.rs
+++ b/build.rs
@@ -11,15 +11,19 @@ fn zip_dir<T>(
it: &mut dyn Iterator<Item = DirEntry>,
prefix: &str,
writer: T,
- method: zip::CompressionMethod,
) -> zip::result::ZipResult<()>
where
T: Write + Seek,
{
let mut zip = zip::ZipWriter::new(writer);
- let options = FileOptions::default()
- .compression_method(method)
+ let mut options = FileOptions::default()
+ .compression_method(zip::CompressionMethod::Zstd)
.unix_permissions(0o755);
+ if let Ok(v) = std::env::var("COMPRESS") {
+ if v == "1" {
+ options = options.compression_level(Some(22));
+ }
+ }
let mut buffer = Vec::new();
for entry in it {
@@ -59,6 +63,5 @@ fn main() -> Result<(), ZipError> {
&mut it.filter_map(|e| e.ok()),
"assets",
File::create(std::env::var("OUT_DIR").unwrap() + "/asset").unwrap(),
- zip::CompressionMethod::Zstd,
)
}
diff --git a/src/access.rs b/src/access.rs
index ad23b51..561d82c 100644
--- a/src/access.rs
+++ b/src/access.rs
@@ -3,6 +3,7 @@ use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
+
use std::ops::Deref;
pub type BoxAccess<'a, D> = Access<'a, Box<D>, D>;
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
index 69db26b..9a80e45 100644
--- a/src/block/distribution.rs
+++ b/src/block/distribution.rs
@@ -1,19 +1,269 @@
//! conveyors ( & ducts )
-use std::error::Error;
-use std::fmt;
+use std::borrow::BorrowMut;
use crate::block::simple::*;
use crate::block::*;
use crate::content;
use crate::data::dynamic::DynType;
-use crate::data::renderer::{load, ImageHolder};
use crate::item;
-use crate::utils::ImageUtils;
+use bobbin_bits::U4;
+use image::imageops::{flip_horizontal_in_place as flip_h, flip_vertical_in_place as flip_v};
+#[cfg(test)]
+macro_rules! dir {
+ (^) => {
+ crate::block::Rotation::Up
+ };
+ (v) => {
+ crate::block::Rotation::Down
+ };
+ (<) => {
+ crate::block::Rotation::Left
+ };
+ (>) => {
+ crate::block::Rotation::Right
+ };
+}
+#[cfg(test)]
+macro_rules! conv {
+ (_) => {
+ None
+ };
+ ($dir:tt) => {
+ Some((
+ &crate::block::distribution::CONVEYOR,
+ crate::block::distribution::dir!($dir),
+ ))
+ };
+}
+#[cfg(test)]
+macro_rules! define {
+ ($a:tt,$b:tt,$c:tt,$d:tt) => {
+ [
+ crate::block::distribution::conv!($a),
+ crate::block::distribution::conv!($b),
+ crate::block::distribution::conv!($c),
+ crate::block::distribution::conv!($d),
+ ]
+ };
+}
+#[cfg(test)]
+pub(crate) use conv;
+#[cfg(test)]
+pub(crate) use define;
+#[cfg(test)]
+pub(crate) use dir;
+
+#[test]
+fn test_mask() {
+ macro_rules! assert {
+ ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => {
+ assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect)
+ };
+ }
+ macro_rules! mask {
+ ($cross:expr, $rot: tt) => {
+ mask(
+ &RenderingContext {
+ position: PositionContext {
+ position: GridPos(5, 5),
+ width: 10,
+ height: 10,
+ },
+ cross: $cross,
+ rotation: dir!($rot),
+ },
+ "conveyor",
+ )
+ };
+ }
+ assert!(_,_,_,_ => ^ => U4::B0000);
+ assert!(v,_,_,_ => > => U4::B1000);
+ assert!(v,v,_,_ => v => U4::B1000);
+ assert!(_,v,>,_ => > => U4::B0000);
+ assert!(v,>,<,> => ^ => U4::B0001);
+ assert!(v,>,>,_ => > => U4::B1000);
+}
-make_simple!(ConveyorBlock);
+fn mask(ctx: &RenderingContext, n: &str) -> U4 {
+ macro_rules! c {
+ ($in: expr, $srot: expr, $name: expr, $at: expr) => {{
+ if let Some((b, rot)) = $in {
+ if b.name() == $name {
+ // if they go down, we must not go up
+ (rot == $at && rot.mirrored(true, true) != $srot) as u8
+ } else {
+ 0
+ }
+ } else {
+ 0
+ }
+ }};
+ }
+ use Rotation::*;
+ let mut x = 0b0000;
+
+ // println!("{:?}, {ctx}", ctx.cross);
+ x |= 8 * c!(ctx.cross[0], ctx.rotation, n, Down);
+ x |= 4 * c!(ctx.cross[1], ctx.rotation, n, Left);
+ x |= 2 * c!(ctx.cross[2], ctx.rotation, n, Up);
+ x |= c!(ctx.cross[3], ctx.rotation, n, Right);
+ U4::from(x)
+}
+
+fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation) -> ImageHolder {
+ mask2tile(mask(ctx, name), rot, name)
+}
+
+const FLIP_X: u8 = 1;
+const FLIP_Y: u8 = 2;
+
+/// TODO figure out if a flip is cheaper than a rotate_270
+fn mask2tile(mask: U4, rot: Rotation, name: &str) -> ImageHolder {
+ use U4::*;
+ // let lo = |index: u8| {
+ // load("distribution/conveyors", &format!("{name}-{index}"))
+ // .unwrap()
+ // .value()
+ // };
+ // r == 5 => flip_v + r - 1
+ macro_rules! p {
+ ($image:literal, $rotation:literal) => {
+ ($image, $rotation, None)
+ };
+ ($image:literal, $rotation:literal, $flipping:expr) => {
+ ($image, $rotation, Some($flipping))
+ };
+ }
+
+ let (index, r, flip) = match mask {
+ // from left
+ B0001 => match rot {
+ Rotation::Down => p!(1, 1, FLIP_Y), // ┐
+ Rotation::Right => p!(0, 0), // ─
+ Rotation::Up => p!(1, 3), // ┘
+ _ => unreachable!(),
+ },
+ // from below
+ B0010 => match rot {
+ Rotation::Left => p!(1, 2), // ┐
+ Rotation::Right => p!(1, 1), // ┌
+ Rotation::Up => p!(0, 3), // │
+ _ => unreachable!(),
+ },
+ // from bottom + left
+ B0011 => match rot {
+ Rotation::Right => p!(2, 0), // ┬
+ Rotation::Up => p!(2, 3, FLIP_Y | FLIP_X), // ┤
+ _ => unreachable!(),
+ },
+ // from right
+ B0100 => match rot {
+ Rotation::Left => p!(0, 2), // ─
+ Rotation::Down => p!(1, 1), // ┌
+ Rotation::Up => p!(1, 1, FLIP_X), // └
+ _ => unreachable!(),
+ },
+ // from sides
+ B0101 => match rot {
+ Rotation::Up => p!(4, 3), // ┴
+ Rotation::Down => p!(4, 1), // ┬
+ _ => unreachable!(),
+ },
+ // from right + down
+ B0110 => match rot {
+ Rotation::Up => p!(2, 3), // ├,
+ Rotation::Left => p!(2, 0, FLIP_X), // ┬
+ _ => unreachable!(),
+ },
+ // from right + down + left
+ B0111 => match rot {
+ Rotation::Up => p!(3, 3), // ┼
+ _ => unreachable!(),
+ },
+ // from above
+ B1000 => match rot {
+ Rotation::Down => p!(0, 1), // │
+ Rotation::Left => p!(1, 0, FLIP_X), // ┘
+ Rotation::Right => p!(1, 0), // └
+ _ => unreachable!(),
+ },
+ // from top and left
+ B1001 => match rot {
+ Rotation::Right => p!(2, 0, FLIP_Y), // ┴
+ Rotation::Down => p!(2, 1), // ┤
+ _ => unreachable!(),
+ },
+ // from top sides
+ B1010 => match rot {
+ Rotation::Right => p!(4, 0), // ├
+ Rotation::Left => p!(4, 3), // ┤
+ _ => unreachable!(),
+ },
+ // from top, left, bottom
+ B1011 => match rot {
+ Rotation::Right => p!(3, 0), // ┼
+ _ => unreachable!(),
+ },
+ // from top and right
+ B1100 => match rot {
+ Rotation::Down => p!(2, 3, FLIP_X), // ├
+ Rotation::Left => p!(2, 2), // ┴
+ _ => unreachable!(),
+ },
+ // from top, left, right
+ B1101 => match rot {
+ Rotation::Down => p!(3, 1), // ┼
+ _ => unreachable!(),
+ },
+ // from top, right, bottom
+ B1110 => match rot {
+ Rotation::Left => p!(3, 0, FLIP_X), // ┼
+ _ => unreachable!(),
+ },
+ B0000 => (
+ 0,
+ match rot {
+ Rotation::Left => 2,
+ Rotation::Right => 0,
+ Rotation::Down => 1,
+ Rotation::Up => 3,
+ },
+ None,
+ ),
+ // B0000 => (0, wrap(rot.count() as i8 - 1, 0, 3) as u8, None),
+ B1111 => unreachable!(),
+ };
+ let mut p = ImageHolder::from(load("distribution/conveyors", &format!("{name}-{index}")));
+ if let Some(op) = flip {
+ let re: &mut RgbaImage = p.borrow_mut();
+ if (op & FLIP_X) != 0 {
+ flip_h(re);
+ }
+ if (op & FLIP_Y) != 0 {
+ flip_v(re);
+ }
+ }
+ if r == 0 {
+ return p;
+ }
+ let mut p = p.own();
+ p.rotate(r);
+ ImageHolder::from(p)
+}
+
+make_simple!(
+ ConveyorBlock,
+ |_, _, name, _, ctx: Option<&RenderingContext>| {
+ if let Some(ctx) = ctx {
+ return Some(tile(ctx, name, ctx.rotation));
+ }
+ None
+ },
+ true
+);
make_simple!(
JunctionBlock,
- |_, _, _, _| None,
+ |_, _, _, _, _| None,
|_, _, _, _, _, buff: &mut crate::data::DataRead| {
// format:
// - iterate 4
@@ -26,34 +276,37 @@ make_simple!(
buff.skip(n * 8)?;
}
Ok(())
- }
+ },
+ false
);
+make_simple!(ControlBlock);
+
make_register! {
"conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1));
"titanium-conveyor" => ConveyorBlock::new(1, false, cost!(Copper: 1, Lead: 1, Titanium: 1));
- "plastanium-conveyor" => ConveyorBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
+ "plastanium-conveyor" => ControlBlock::new(1, false, cost!(Graphite: 1, Silicon: 1, Plastanium: 1));
"armored-conveyor" => ConveyorBlock::new(1, false, cost!(Metaglass: 1, Thorium: 1, Plastanium: 1));
"junction" => JunctionBlock::new(1, true, cost!(Copper: 2));
"bridge-conveyor" => BridgeBlock::new(1, false, cost!(Copper: 6, Lead: 6), 4, true);
"phase-conveyor" => BridgeBlock::new(1, false, cost!(Lead: 10, Graphite: 10, Silicon: 7, PhaseFabric: 5), 12, true);
"sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
"inverted-sorter" => ItemBlock::new(1, true, cost!(Copper: 2, Lead: 2));
- "router" => ConveyorBlock::new(1, true, cost!(Copper: 3));
- "distributor" => ConveyorBlock::new(2, true, cost!(Copper: 4, Lead: 4));
- "overflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2));
- "underflow-gate" => ConveyorBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "router" => ControlBlock::new(1, true, cost!(Copper: 3));
+ "distributor" => ControlBlock::new(2, true, cost!(Copper: 4, Lead: 4));
+ "overflow-gate" => ControlBlock::new(1, true, cost!(Copper: 4, Lead: 2));
+ "underflow-gate" => ControlBlock::new(1, true, cost!(Copper: 4, Lead: 2));
"mass-driver" => BridgeBlock::new(3, true, cost!(Lead: 125, Titanium: 125, Thorium: 50, Silicon: 75), 55, false);
"duct" => ConveyorBlock::new(1, false, cost!(Beryllium: 1));
"armored-duct" => ConveyorBlock::new(1, false, cost!(Beryllium: 2, Tungsten: 1));
"duct-router" => ItemBlock::new(1, true, cost!(Beryllium: 10));
- "overflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
- "underflow-duct" => ConveyorBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "overflow-duct" => ControlBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
+ "underflow-duct" => ControlBlock::new(1, true, cost!(Graphite: 8, Beryllium: 8));
"duct-bridge" => BridgeBlock::new(1, true, cost!(Beryllium: 20), 3, true);
"duct-unloader" => ItemBlock::new(1, true, cost!(Graphite: 20, Silicon: 20, Tungsten: 10));
- "surge-conveyor" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
- "surge-router" => ConveyorBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
- "unit-cargo-loader" => ConveyorBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20));
+ "surge-conveyor" => ControlBlock::new(1, false, cost!(SurgeAlloy: 1, Tungsten: 1));
+ "surge-router" => ControlBlock::new(1, false, cost!(SurgeAlloy: 5, Tungsten: 1)); // not symmetric
+ "unit-cargo-loader" => ControlBlock::new(3, true, cost!(Silicon: 80, SurgeAlloy: 50, Oxide: 20));
"unit-cargo-unload-point" => ItemBlock::new(2, true, cost!(Silicon: 60, Tungsten: 60));
// sandbox only
"item-source" => ItemBlock::new(1, true, &[]);
@@ -122,7 +375,13 @@ impl BlockLogic for ItemBlock {
}
}
- fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> {
+ fn draw(
+ &self,
+ category: &str,
+ name: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ ) -> Option<ImageHolder> {
if !matches!(
name,
"unloader" | "item-source" | "sorter" | "inverted-sorter"
@@ -146,21 +405,16 @@ impl BlockLogic for ItemBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid config ({0}) for item")]
pub struct ItemConvertError(pub i32);
-impl fmt::Display for ItemConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid config ({}) for item", self.0)
- }
-}
-
-impl Error for ItemConvertError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ItemDeserializeError {
+ #[error("expected Item but got {0:?}")]
ContentType(content::Type),
- NotFound(item::TryFromU16Error),
+ #[error("target item not found")]
+ NotFound(#[from] item::TryFromU16Error),
}
impl ItemDeserializeError {
@@ -172,34 +426,6 @@ impl ItemDeserializeError {
}
}
-impl From<item::TryFromU16Error> for ItemDeserializeError {
- fn from(err: item::TryFromU16Error) -> Self {
- Self::NotFound(err)
- }
-}
-
-impl fmt::Display for ItemDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::ContentType(have) => write!(
- f,
- "expected content {:?} but got {have:?}",
- content::Type::Item
- ),
- Self::NotFound(..) => f.write_str("target item not found"),
- }
- }
-}
-
-impl Error for ItemDeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::NotFound(e) => Some(e),
- _ => None,
- }
- }
-}
-
pub struct BridgeBlock {
size: u8,
symmetric: bool,
@@ -244,8 +470,8 @@ impl BlockLogic for BridgeBlock {
y,
})));
}
- let dx = i32::from(x) - i32::from(pos.0);
- let dy = i32::from(y) - i32::from(pos.1);
+ let dx = i32::from(x) - pos.0 as i32;
+ let dy = i32::from(y) - pos.1 as i32;
Ok(DynData::Point2(dx, dy))
}
@@ -334,16 +560,9 @@ impl BlockLogic for BridgeBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("invalid coordinates ({x}, {y}) for bridge")]
pub struct BridgeConvertError {
pub x: i16,
pub y: i16,
}
-
-impl fmt::Display for BridgeConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid coordinate ({} / {}) for bridge", self.x, self.y)
- }
-}
-
-impl Error for BridgeConvertError {}
diff --git a/src/block/drills.rs b/src/block/drills.rs
index 67d72f4..f3d2e8e 100644
--- a/src/block/drills.rs
+++ b/src/block/drills.rs
@@ -3,7 +3,7 @@ use crate::block::make_register;
use crate::block::simple::{cost, make_simple};
use crate::data::renderer::*;
-make_simple!(DrillBlock, |me: &DrillBlock, _, name, _| {
+make_simple!(DrillBlock, |me: &DrillBlock, _, name, _, _| {
if matches!(name, "cliff-crusher" | "large-plasma-bore" | "plasma-bore") {
const SFX: &[&str; 3] = &["", "-top", "-rotator"];
return Some(ImageHolder::Own(read_with("drills", name, SFX, me.size)));
diff --git a/src/block/environment.rs b/src/block/environment.rs
index 9eee40c..0796230 100644
--- a/src/block/environment.rs
+++ b/src/block/environment.rs
@@ -11,7 +11,7 @@ macro_rules! register_env {
$($field => EnvironmentBlock::new($size, true, &[]);)*
);
- make_simple!(EnvironmentBlock, |_, _, name, _| {
+ make_simple!(EnvironmentBlock, |_, _, name, _, _| {
let mut rand = StdRand::seed(ClockSeed::default().next_u64());
match name {
$($field => {
diff --git a/src/block/liquid.rs b/src/block/liquid.rs
index 888bd0d..d74fc9d 100644
--- a/src/block/liquid.rs
+++ b/src/block/liquid.rs
@@ -1,6 +1,5 @@
//! liquid related things
-use std::error::Error;
-use std::fmt;
+use thiserror::Error;
use crate::block::distribution::BridgeBlock;
use crate::block::simple::*;
@@ -102,7 +101,13 @@ impl BlockLogic for FluidBlock {
}
}
- fn draw(&self, category: &str, name: &str, state: Option<&State>) -> Option<ImageHolder> {
+ fn draw(
+ &self,
+ category: &str,
+ name: &str,
+ state: Option<&State>,
+ _: Option<&RenderingContext>,
+ ) -> Option<ImageHolder> {
let mut p = load(category, name).unwrap().clone();
if let Some(state) = state {
if let Some(s) = Self::get_state(state) {
@@ -117,21 +122,16 @@ impl BlockLogic for FluidBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
+#[error("invalid config ({0}) for fluid")]
pub struct FluidConvertError(pub i32);
-impl fmt::Display for FluidConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid config ({}) for fluid", self.0)
- }
-}
-
-impl Error for FluidConvertError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
pub enum FluidDeserializeError {
+ #[error("expected Fluid but got {0:?}")]
ContentType(content::Type),
- NotFound(fluid::TryFromU16Error),
+ #[error("fluid not found")]
+ NotFound(#[from] fluid::TryFromU16Error),
}
impl FluidDeserializeError {
@@ -142,31 +142,3 @@ impl FluidDeserializeError {
}
}
}
-
-impl From<fluid::TryFromU16Error> for FluidDeserializeError {
- fn from(err: fluid::TryFromU16Error) -> Self {
- Self::NotFound(err)
- }
-}
-
-impl fmt::Display for FluidDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::ContentType(have) => write!(
- f,
- "expected content {:?} but got {have:?}",
- content::Type::Fluid
- ),
- Self::NotFound(..) => f.write_str("fluid not found"),
- }
- }
-}
-
-impl Error for FluidDeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::NotFound(e) => Some(e),
- _ => None,
- }
- }
-}
diff --git a/src/block/logic.rs b/src/block/logic.rs
index 24c7b2c..79941c4 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -1,18 +1,11 @@
//! logic processors and stuff
use std::borrow::Cow;
-use std::error::Error;
-use std::fmt;
use std::string::FromUtf8Error;
-use flate2::{
- Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress,
- FlushDecompress, Status,
-};
-
use crate::block::simple::*;
use crate::block::*;
use crate::data::dynamic::DynType;
-use crate::data::{self, DataRead, DataWrite};
+use crate::data::{self, CompressError, DataRead, DataWrite};
make_simple!(LogicBlock);
@@ -167,37 +160,9 @@ impl BlockLogic for ProcessorLogic {
match data {
DynData::Empty => Ok(Some(Self::create_state(ProcessorState::default()))),
DynData::ByteArray(arr) => {
- let mut input = arr.as_ref();
- let mut dec = Decompress::new(true);
- let mut raw = Vec::<u8>::new();
- raw.reserve(1024);
- loop {
- let t_in = dec.total_in();
- let t_out = dec.total_out();
- let res = ProcessorDeserializeError::forward(dec.decompress_vec(
- input,
- &mut raw,
- FlushDecompress::Finish,
- ))?;
- if dec.total_in() > t_in {
- // we have to advance input every time, decompress_vec only knows the output position
- input = &input[(dec.total_in() - t_in) as usize..];
- }
- match res {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if dec.total_in() == t_in && dec.total_out() == t_out {
- // protect against looping forever
- return Err(DeserializeError::Custom(Box::new(
- ProcessorDeserializeError::DecompressStall,
- )));
- }
- raw.reserve(1024);
- }
- let mut buff = DataRead::new(&raw);
+ let input = arr.as_ref();
+ let buff = DataRead::new(input).deflate()?;
+ let mut buff = DataRead::new(&buff);
let ver = ProcessorDeserializeError::forward(buff.read_u8())?;
if ver != 1 {
return Err(DeserializeError::Custom(Box::new(
@@ -279,48 +244,23 @@ impl BlockLogic for ProcessorLogic {
ProcessorSerializeError::forward(rbuff.write_i16(link.x))?;
ProcessorSerializeError::forward(rbuff.write_i16(link.y))?;
}
- let mut input = rbuff.get_written();
- let mut comp = Compress::new(Compression::default(), true);
- let mut dst = Vec::<u8>::new();
- dst.reserve(1024);
- loop {
- let t_in = comp.total_in();
- let t_out = comp.total_out();
- let res = ProcessorSerializeError::forward(comp.compress_vec(
- input,
- &mut dst,
- FlushCompress::Finish,
- ))?;
- if comp.total_in() > t_in {
- // we have to advance input every time, compress_vec only knows the output position
- input = &input[(comp.total_in() - t_in) as usize..];
- }
- match res {
- // there's no more input (and the flush mode says so), we need to reserve additional space
- Status::Ok | Status::BufError => (),
- // input was already at the end, so this is referring to the output
- Status::StreamEnd => break,
- }
- if comp.total_in() == t_in && comp.total_out() == t_out {
- // protect against looping forever
- return Err(SerializeError::Custom(Box::new(
- ProcessorSerializeError::CompressStall,
- )));
- }
- dst.reserve(1024);
- }
- Ok(DynData::ByteArray(dst))
+ let mut out = DataWrite::default();
+ rbuff.inflate(&mut out)?;
+ Ok(DynData::ByteArray(out.consume()))
}
}
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub enum ProcessorDeserializeError {
- Read(data::ReadError),
- Decompress(DecompressError),
- DecompressStall,
- FromUtf8(FromUtf8Error),
+ #[error("failed to read state data")]
+ Read(#[from] data::ReadError),
+ #[error("malformed utf-8 in processor code")]
+ FromUtf8(#[from] FromUtf8Error),
+ #[error("unsupported version ({0})")]
Version(u8),
+ #[error("invalid code length ({0})")]
CodeLength(i32),
+ #[error("invalid link count {0}")]
LinkCount(i32),
}
@@ -333,54 +273,12 @@ impl ProcessorDeserializeError {
}
}
-impl From<data::ReadError> for ProcessorDeserializeError {
- fn from(value: data::ReadError) -> Self {
- Self::Read(value)
- }
-}
-
-impl From<DecompressError> for ProcessorDeserializeError {
- fn from(value: DecompressError) -> Self {
- Self::Decompress(value)
- }
-}
-
-impl From<FromUtf8Error> for ProcessorDeserializeError {
- fn from(value: FromUtf8Error) -> Self {
- Self::FromUtf8(value)
- }
-}
-
-impl fmt::Display for ProcessorDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Read(..) => f.write_str("failed to read state data"),
- Self::Decompress(..) => f.write_str("zlib decompression failed"),
- Self::DecompressStall => f.write_str("decompressor stalled before completion"),
- Self::FromUtf8(..) => f.write_str("malformed utf-8 in processor code"),
- Self::Version(ver) => write!(f, "unsupported version ({ver})"),
- Self::CodeLength(len) => write!(f, "invalid code length ({len})"),
- Self::LinkCount(cnt) => write!(f, "invalid link count ({cnt})"),
- }
- }
-}
-
-impl Error for ProcessorDeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::Decompress(e) => Some(e),
- Self::FromUtf8(e) => Some(e),
- _ => None,
- }
- }
-}
-
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub enum ProcessorSerializeError {
- Write(data::WriteError),
- Compress(CompressError),
- CompressEof(usize),
- CompressStall,
+ #[error("failed to write state data")]
+ Write(#[from] data::WriteError),
+ #[error(transparent)]
+ Compress(#[from] CompressError),
}
impl ProcessorSerializeError {
@@ -392,41 +290,6 @@ impl ProcessorSerializeError {
}
}
-impl From<data::WriteError> for ProcessorSerializeError {
- fn from(value: data::WriteError) -> Self {
- Self::Write(value)
- }
-}
-
-impl From<CompressError> for ProcessorSerializeError {
- fn from(value: CompressError) -> Self {
- Self::Compress(value)
- }
-}
-
-impl fmt::Display for ProcessorSerializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Write(..) => f.write_str("failed to write state data"),
- Self::Compress(..) => f.write_str("zlib compression failed"),
- Self::CompressEof(remain) => write!(
- f,
- "compression overflow with {remain} bytes of input remaining"
- ),
- Self::CompressStall => f.write_str("compressor stalled before completion"),
- }
- }
-}
-
-impl Error for ProcessorSerializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::Compress(e) => Some(e),
- _ => None,
- }
- }
-}
-
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct ProcessorLink {
name: String,
@@ -529,38 +392,18 @@ impl ProcessorState {
}
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum CodeError {
+ #[error("code too long ({0} bytes)")]
TooLong(usize),
}
-impl fmt::Display for CodeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::TooLong(len) => write!(f, "code too long ({len} bytes)"),
- }
- }
-}
-
-impl Error for CodeError {}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum CreateError {
+ #[error("link name too long ({0} bytes)")]
NameLength(usize),
+ #[error("there is already a link named {0}")]
DuplicateName(String),
+ #[error("link {name} already points to ({x}, {y})")]
DuplicatePos { name: String, x: i16, y: i16 },
}
-
-impl fmt::Display for CreateError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::NameLength(len) => write!(f, "link name too long ({len} bytes)"),
- Self::DuplicateName(name) => write!(f, "there already is a link named {name}"),
- Self::DuplicatePos { name, x, y } => {
- write!(f, "link {name} already points to {x} / {y}")
- }
- }
- }
-}
-
-impl Error for CreateError {}
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 642518b..e5e8a49 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -11,26 +11,26 @@ use std::fmt;
use crate::access::BoxAccess;
use crate::data::dynamic::{DynData, DynType};
use crate::data::map::EntityMapping;
-use crate::data::renderer::ImageHolder;
+use crate::data::{self, renderer::*, CompressError};
use crate::data::{DataRead, GridPos, ReadError as DataReadError};
use crate::item::storage::ItemStorage;
use crate::registry::RegistryEntry;
-pub mod campaign;
-pub mod content;
-pub mod defense;
-pub mod distribution;
-pub mod drills;
-pub mod environment;
-pub mod liquid;
-pub mod logic;
-pub mod payload;
-pub mod power;
-pub mod production;
-pub mod simple;
-pub mod storage;
-pub mod turrets;
-pub mod walls;
+macro_rules! mods {
+ ($($mod:ident)*) => {
+ $(pub mod $mod;)*
+
+ pub mod all {
+ $(pub use crate::block::$mod::*;)*
+ }
+ }
+}
+
+mods! {
+ campaign content defense distribution drills environment liquid logic payload power production storage turrets walls
+}
+
+mod simple;
pub type State = Box<dyn Any + Sync + Send>;
pub trait BlockLogic {
@@ -53,9 +53,21 @@ pub trait BlockLogic {
fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError>;
- fn draw(&self, _category: &str, _name: &str, _state: Option<&State>) -> Option<ImageHolder> {
+ #[allow(unused_variables)]
+ fn draw(
+ &self,
+ category: &str,
+ name: &str,
+ state: Option<&State>,
+ context: Option<&RenderingContext>,
+ ) -> Option<ImageHolder> {
None
}
+
+ fn want_context(&self) -> bool {
+ false
+ }
+
// TODO: use data
#[allow(unused_variables)]
fn read(
@@ -96,9 +108,10 @@ macro_rules! impl_block {
}
pub(crate) use impl_block;
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub enum DataConvertError {
- Custom(Box<dyn Error + Sync + Send>),
+ #[error(transparent)]
+ Custom(#[from] Box<dyn Error + Sync + Send>),
}
impl DataConvertError {
@@ -110,26 +123,14 @@ impl DataConvertError {
}
}
-impl fmt::Display for DataConvertError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Custom(e) => e.fmt(f),
- }
- }
-}
-
-impl Error for DataConvertError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::Custom(e) => e.source(),
- }
- }
-}
-
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub enum DeserializeError {
+ #[error(transparent)]
+ DecompressError(#[from] data::DecompressError),
+ #[error("expected type {expect:?} but got {have:?}")]
InvalidType { have: DynType, expect: DynType },
- Custom(Box<dyn Error + Sync + Send>),
+ #[error(transparent)]
+ Custom(#[from] Box<dyn Error + Sync + Send>),
}
impl DeserializeError {
@@ -141,29 +142,12 @@ impl DeserializeError {
}
}
-impl fmt::Display for DeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::InvalidType { have, expect } => {
- write!(f, "expected type {expect:?} but got {have:?}")
- }
- Self::Custom(e) => e.fmt(f),
- }
- }
-}
-
-impl Error for DeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::Custom(e) => e.source(),
- _ => None,
- }
- }
-}
-
-#[derive(Debug)]
+#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
- Custom(Box<dyn Error + Sync + Send>),
+ #[error(transparent)]
+ Custom(#[from] Box<dyn Error + Sync + Send>),
+ #[error(transparent)]
+ Compress(#[from] CompressError),
}
impl SerializeError {
@@ -175,22 +159,6 @@ impl SerializeError {
}
}
-impl fmt::Display for SerializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Custom(e) => e.fmt(f),
- }
- }
-}
-
-impl Error for SerializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::Custom(e) => e.source(),
- }
- }
-}
-
/// a block. put it in stuff!
pub struct Block {
category: Cow<'static, str>,
@@ -227,12 +195,20 @@ impl Block {
&self.name
}
+ /// should you send context to [`image`]?
+ pub fn wants_context(&self) -> bool {
+ self.logic.as_ref().want_context()
+ }
+
/// draw this block, with this state
- pub fn image(&self, state: Option<&State>) -> ImageHolder {
- if let Some(p) = self.logic.as_ref().draw(&self.category, &self.name, state) {
+ pub fn image(&self, state: Option<&State>, context: Option<&RenderingContext>) -> ImageHolder {
+ if let Some(p) = self
+ .logic
+ .as_ref()
+ .draw(&self.category, &self.name, state, context)
+ {
return p;
}
- use crate::data::renderer::read;
ImageHolder::Own(read(&self.category, &self.name, self.get_size()))
}
@@ -301,7 +277,7 @@ impl Block {
impl fmt::Debug for Block {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name: &str = &self.name;
- write!(f, "Block {{ name: {name:?} }}")
+ write!(f, "Block<{name:?}>")
}
}
@@ -322,6 +298,28 @@ pub enum Rotation {
impl Rotation {
#[must_use]
+ /// count rotations
+ pub fn count(self) -> u8 {
+ match self {
+ Self::Up => 0,
+ Self::Right => 1,
+ Self::Down => 2,
+ Self::Left => 3,
+ }
+ }
+
+ #[must_use]
+ /// character of this rot (Right => >, Up => ^, Left => <, Down => v)
+ pub fn ch(self) -> char {
+ match self {
+ Rotation::Right => '>',
+ Rotation::Up => '^',
+ Rotation::Left => '<',
+ Rotation::Down => 'v',
+ }
+ }
+
+ #[must_use]
/// mirror the directions.
pub fn mirrored(self, horizontally: bool, vertically: bool) -> Self {
match self {
@@ -453,7 +451,7 @@ macro_rules! make_register {
std::borrow::Cow::Borrowed($field), $crate::access::Access::Borrowed(&$logic));
)+
- pub fn register(reg: &mut $crate::block::BlockRegistry<'_>) {
+ pub(crate) fn register(reg: &mut $crate::block::BlockRegistry<'_>) {
$(assert!(reg.register(&[<$field:snake:upper>]).is_ok(), "duplicate block {:?}", $field);)+
}
}};
diff --git a/src/block/payload.rs b/src/block/payload.rs
index c743934..83f9700 100644
--- a/src/block/payload.rs
+++ b/src/block/payload.rs
@@ -1,6 +1,5 @@
//! payload related bits and bobs
-use std::error::Error;
-use std::fmt;
+use thiserror::Error;
use crate::block::content::Type as BlockEnum;
use crate::block::distribution::BridgeBlock;
@@ -145,35 +144,17 @@ impl BlockLogic for AssemblerBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
+#[error("invalid unit index ({idx}, valid: {count})")]
pub struct AssemblerDeserializeError {
pub idx: i32,
pub count: i32,
}
-impl fmt::Display for AssemblerDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(
- f,
- "invalid unit index ({}, #valid: {})",
- self.idx, self.count
- )
- }
-}
-
-impl Error for AssemblerDeserializeError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
+#[error("invalid unit {0:?}")]
pub struct AssemblerSerializeError(unit::Type);
-impl fmt::Display for AssemblerSerializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "invalid unit ({:?}) is not valid", self.0)
- }
-}
-
-impl Error for AssemblerSerializeError {}
-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Payload {
Empty,
@@ -291,11 +272,14 @@ impl BlockLogic for PayloadBlock {
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
pub enum PayloadDeserializeError {
+ #[error("expected Unit or Block but got {0:?}")]
ContentType(content::Type),
- BlockNotFound(block::content::TryFromU16Error),
- UnitNotFound(unit::TryFromU16Error),
+ #[error("payload block not found")]
+ BlockNotFound(#[from] block::content::TryFromU16Error),
+ #[error("payload unit not found")]
+ UnitNotFound(#[from] unit::TryFromU16Error),
}
impl PayloadDeserializeError {
@@ -306,40 +290,3 @@ impl PayloadDeserializeError {
}
}
}
-
-impl From<block::content::TryFromU16Error> for PayloadDeserializeError {
- fn from(err: block::content::TryFromU16Error) -> Self {
- Self::BlockNotFound(err)
- }
-}
-
-impl From<unit::TryFromU16Error> for PayloadDeserializeError {
- fn from(err: unit::TryFromU16Error) -> Self {
- Self::UnitNotFound(err)
- }
-}
-
-impl fmt::Display for PayloadDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::ContentType(have) => write!(
- f,
- "expected content {:?} or {:?} but got {have:?}",
- content::Type::Block,
- content::Type::Unit
- ),
- Self::BlockNotFound(..) => f.write_str("payload block not found"),
- Self::UnitNotFound(..) => f.write_str("payload unit not found"),
- }
- }
-}
-
-impl Error for PayloadDeserializeError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- Self::BlockNotFound(e) => Some(e),
- Self::UnitNotFound(e) => Some(e),
- _ => None,
- }
- }
-}
diff --git a/src/block/power.rs b/src/block/power.rs
index f5048c6..2c21cad 100644
--- a/src/block/power.rs
+++ b/src/block/power.rs
@@ -1,6 +1,5 @@
//! power connection and generation
-use std::error::Error;
-use std::fmt;
+use thiserror::Error;
use crate::block::simple::*;
use crate::block::*;
@@ -117,8 +116,9 @@ impl BlockLogic for ConnectorBlock {
}
}
-#[derive(Debug)]
+#[derive(Debug, Error)]
pub enum ConnectorDeserializeError {
+ #[error("too many links ({have} but only {max} allowed)")]
LinkCount { have: usize, max: u8 },
}
@@ -131,18 +131,6 @@ impl ConnectorDeserializeError {
}
}
-impl fmt::Display for ConnectorDeserializeError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::LinkCount { have, max } => {
- write!(f, "too many links ({have} but only {max} supported)")
- }
- }
- }
-}
-
-impl Error for ConnectorDeserializeError {}
-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RGBA(u8, u8, u8, u8);
diff --git a/src/block/production.rs b/src/block/production.rs
index 7d8e7df..f5fa47b 100644
--- a/src/block/production.rs
+++ b/src/block/production.rs
@@ -44,7 +44,7 @@ make_register! {
make_simple!(
ProductionBlock,
- |_, _, _, _| None,
+ |_, _, _, _, _| None,
|_, _, _, _, _, buff: &mut crate::data::DataRead| {
// format:
// - progress: `f32`
@@ -57,7 +57,7 @@ make_simple!(
make_simple!(
HeatCrafter,
- |_, _, _, _| None,
+ |_, _, _, _, _| None,
|_, _, _, _, _, buff: &mut crate::data::DataRead| {
// format:
// - progress: `f32`
diff --git a/src/block/simple.rs b/src/block/simple.rs
index 2dca7fd..89417c6 100644
--- a/src/block/simple.rs
+++ b/src/block/simple.rs
@@ -22,8 +22,10 @@ macro_rules! state_impl {
pub(crate) use state_impl;
+/// draw is called with self, category, name, state, context
+/// read is called with self, category, name, reg, entity_mapping, buff
macro_rules! make_simple {
- ($name: ident, $draw: expr, $read: expr) => {
+ ($name: ident, $draw: expr, $read: expr, $wants_context: literal) => {
pub struct $name {
size: u8,
symmetric: bool,
@@ -87,8 +89,13 @@ macro_rules! make_simple {
category: &str,
name: &str,
state: Option<&crate::block::State>,
+ context: Option<&crate::data::renderer::RenderingContext>,
) -> Option<crate::data::renderer::ImageHolder> {
- $draw(self, category, name, state)
+ $draw(self, category, name, state, context)
+ }
+
+ fn want_context(&self) -> bool {
+ $wants_context
}
fn read(
@@ -104,10 +111,21 @@ macro_rules! make_simple {
}
};
($name: ident, $draw: expr) => {
- crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(()));
+ crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(()), false);
+ };
+ ($name: ident, $draw: expr, $wants_context: literal) => {
+ crate::block::simple::make_simple!($name, $draw, |_, _, _, _, _, _| Ok(()), $wants_context);
+ };
+ ($name: ident, $draw: expr, $read: expr) => {
+ crate::block::simple::make_simple!($name, $draw, $read, false);
};
($name: ident) => {
- crate::block::simple::make_simple!($name, |_, _, _, _| None, |_, _, _, _, _, _| { Ok(()) });
+ crate::block::simple::make_simple!(
+ $name,
+ |_, _, _, _, _| None,
+ |_, _, _, _, _, _| { Ok(()) },
+ false
+ );
};
}
pub(crate) use make_simple;
diff --git a/src/block/turrets.rs b/src/block/turrets.rs
index 160c991..1a931ea 100644
--- a/src/block/turrets.rs
+++ b/src/block/turrets.rs
@@ -34,7 +34,7 @@ make_register! {
use crate::data::renderer::*;
use crate::utils::ImageUtils;
-crate::block::simple::make_simple!(TurretBlock, |me: &Self, _, name, _| {
+crate::block::simple::make_simple!(TurretBlock, |me: &Self, _, name, _, _| {
let path = match name {
"breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre"
| "scathe" | "malign" => format!("bases/reinforced-block-{}", me.size),
diff --git a/src/block/walls.rs b/src/block/walls.rs
index d746cdf..a0e3c45 100644
--- a/src/block/walls.rs
+++ b/src/block/walls.rs
@@ -4,7 +4,7 @@ use crate::block::*;
use crate::data::dynamic::DynType;
use crate::data::renderer::{load, read_with, ImageHolder, TOP};
-make_simple!(WallBlock, |_, _, name, _| {
+make_simple!(WallBlock, |_, _, name, _, _| {
if name == "thruster" {
const SFX: &[&str; 1] = &[TOP];
return Some(ImageHolder::Own(read_with(
diff --git a/src/data/map.rs b/src/data/map.rs
index 4b1e5bd..5431d01 100644
--- a/src/data/map.rs
+++ b/src/data/map.rs
@@ -1,6 +1,6 @@
//! the map module
//! ### format
-//! note: utf = `len<u16>` + utf8(read(len))
+//! note: utf = `len<u16>` + `utf8(read(len))`
//!
//! note: each section has a `u32` denoting its length
//!
@@ -12,10 +12,10 @@
//! - tag section `<u32>`
//! - 1 byte of idk (skip)
//! - string map (`u16` for map len, iterate each, read `utf`)
-//! - 4 bytes of idk (skip)
//! - content header section `<u32>`:
-//! - iterate `i8` (should = `8`)'//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u1\'6` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7)
-//! - these types all have their own modules: [`crate::item`], [`crate::block::content`], [`crate::fluid`], [`crate::modifier`], [`crate::unit`], [`crate::data::weather`], [`crate::data::sector`], [`crate::data::planet`]
+//! - iterate `i8` (should = `8`)'
+//! - the type: `i8` (0: item, block: 1, liquid: 4, status: 5, unit: 6, weather: 7, sector: 9, planet: 13//! - item count: `u1\'6` (item: 22, block: 412, liquid: 11, status: 21, unit: 66, weather: 6, sector: 35, planet: 7)
+//! - these types all have their own modules: [`item`], [`content`], [`fluid`], [`modifier`], [`mod@unit`], [`weather`], [`sector`], [`planet`]
//! - iterate `u16`
//! - name: `utf`
//! - map section `<u32>`
@@ -59,7 +59,7 @@
//! - team = `team#<u32>`
//! - iterate `plans<u32>`
//! - x: `u16`, y: `u16`, rot: `u16`, id: `u16`
-//! - o: `DynData` (refer to [crate::data::dynamic::DynSerializer])
+//! - o: `DynData` (refer to [`DynSerializer`])
//! - world entities
//! - iterate `u32`
//! - len: `u16`
@@ -70,33 +70,42 @@
//! - id: `u32`
//! - entity read
use std::collections::HashMap;
+use std::ops::{Index, IndexMut};
use thiserror::Error;
use crate::block::content::Type as BlockEnum;
-use crate::block::{Block, BlockRegistry, Rotation};
+use crate::block::{environment, Block, BlockRegistry, Rotation};
use crate::data::dynamic::DynSerializer;
use crate::data::renderer::*;
use crate::data::DataRead;
use crate::fluid::Type as Fluid;
-use crate::item::storage::Storage;
-use crate::item::Type as Item;
+use crate::item::{storage::Storage, Type as Item};
use crate::team::Team;
+#[allow(unused_imports)]
+use crate::{block::content, data::*, fluid, item, modifier, unit};
-use super::GridPos;
use super::Serializer;
use crate::content::Content;
use crate::utils::image::ImageUtils;
/// a tile in a map
+#[derive(Clone)]
pub struct Tile<'l> {
- pub pos: GridPos,
pub floor: &'l Block,
pub ore: Option<&'l Block>,
- pub build: Option<Build<'l>>,
+ build: Option<Build<'l>>,
}
pub type EntityMapping = HashMap<u8, Box<dyn Content>>;
impl<'l> Tile<'l> {
+ pub fn new(floor: &'l Block, ore: Option<&'l Block>) -> Self {
+ Self {
+ floor,
+ ore,
+ build: None,
+ }
+ }
+
fn set_block(&mut self, block: &'l Block) {
self.build = Some(Build {
block,
@@ -108,6 +117,10 @@ impl<'l> Tile<'l> {
});
}
+ pub fn build(&self) -> Option<&Build<'l>> {
+ self.build.as_ref()
+ }
+
/// check if this tile contains a building.
pub fn has_building(&self) -> bool {
if let Some(b) = &self.build {
@@ -128,14 +141,14 @@ impl<'l> Tile<'l> {
1
}
- pub fn image(&self) -> ImageHolder {
+ pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder {
// building covers floore
let i = if let Some(b) = &self.build {
- b.image()
+ b.image(context)
} else {
- let mut i = self.floor.image(None).own();
+ let mut i = self.floor.image(None, context).own();
if let Some(ore) = self.ore {
- i.overlay(ore.image(None).borrow(), 0, 0);
+ i.overlay(ore.image(None, context).borrow(), 0, 0);
}
ImageHolder::from(i)
};
@@ -147,14 +160,12 @@ impl std::fmt::Debug for Tile<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
- "Tile<{},{}>@{}+{}{}",
- self.pos.0,
- self.pos.1,
+ "Tile@{}{}{}",
self.floor.name(),
if let Some(ore) = &self.ore {
- ore.name()
+ format!("+{}", ore.name())
} else {
- ""
+ "".into()
},
if let Some(build) = &self.build {
format!(":{}", build.block.name())
@@ -165,8 +176,32 @@ impl std::fmt::Debug for Tile<'_> {
}
}
+impl<'l> BlockState<'l> for Tile<'l> {
+ fn get_block(&self) -> Option<&'l Block> {
+ Some(self.build()?.block)
+ }
+}
+
+impl RotationState for Tile<'_> {
+ fn get_rotation(&self) -> Option<Rotation> {
+ Some(self.build()?.rotation)
+ }
+}
+
+impl RotationState for Option<Tile<'_>> {
+ fn get_rotation(&self) -> Option<Rotation> {
+ self.as_ref().unwrap().get_rotation()
+ }
+}
+
+impl<'l> BlockState<'l> for Option<Tile<'_>> {
+ fn get_block(&'l self) -> Option<&'l Block> {
+ self.as_ref().unwrap().get_block()
+ }
+}
+
/// a build on a tile in a map
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Build<'l> {
pub block: &'l Block,
pub items: Storage<Item>,
@@ -178,79 +213,163 @@ pub struct Build<'l> {
}
impl Build<'_> {
- pub fn image(&self) -> ImageHolder {
- self.block.image(None)
+ pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder {
+ self.block.image(None, context)
}
pub fn read(
&mut self,
buff: &mut DataRead<'_>,
- reg: &BlockRegistry,
- map: &EntityMapping,
+ _reg: &BlockRegistry,
+ _map: &EntityMapping,
) -> Result<(), ReadError> {
// health
let _ = buff.read_f32()?; // 4
- let rot = dbg!(buff.read_u8()?); // 5
+ let rot = buff.read_u8()?; // 5
self.rotation = Rotation::try_from(rot & 127).unwrap_or(Rotation::Up);
if (rot & 128) == 0 {
return Err(ReadError::Version(rot & 128));
}
- let _t = dbg!(buff.read_u8()?); // 6
- let _v = dbg!(buff.read_u8()?); // 7
- let mask = dbg!(buff.read_u8()?); // 8
- if dbg!((mask & 1) != 0) {
- self.items.clear();
- // 10
- for _ in 0..dbg!(buff.read_u16()?) {
- let item = buff.read_u16()?;
- let amount = buff.read_u32()?;
- if let Ok(item) = Item::try_from(item) {
- self.items.set(item, amount);
- }
- }
- }
- if mask & 2 == 0 {
- let n = buff.read_u16()? as usize;
- buff.skip((n * 4) + 1)?;
- }
- if mask & 4 == 0 {
- self.liquids.clear();
- for _ in 0..buff.read_u16()? {
- let fluid = buff.read_u16()?;
- let amount = buff.read_f32()?;
- if let Ok(fluid) = Fluid::try_from(fluid) {
- self.liquids.set(fluid, (amount * 100.0) as u32);
- }
- }
- }
+ let _t = buff.read_u8()?; // 6
+ let _v = buff.read_u8()?; // 7
+ let _mask = buff.read_u8()?; // 8
+
+ // if (mask & 1) != 0 {
+ // self.items.clear();
+ // // 10
+ // for _ in 0..dbg!(buff.read_u16()?) {
+ // let item = buff.read_u16()?;
+ // let amount = buff.read_u32()?;
+ // if let Ok(item) = Item::try_from(item) {
+ // self.items.set(item, amount);
+ // }
+ // }
+ // }
+ // if mask & 2 == 0 {
+ // let n = buff.read_u16()? as usize;
+ // buff.skip((n * 4) + 1)?;
+ // }
+ // if mask & 4 == 0 {
+ // self.liquids.clear();
+ // for _ in 0..buff.read_u16()? {
+ // let fluid = buff.read_u16()?;
+ // let amount = buff.read_f32()?;
+ // if let Ok(fluid) = Fluid::try_from(fluid) {
+ // self.liquids.set(fluid, (amount * 100.0) as u32);
+ // }
+ // }
+ // }
// "efficiency"?
- let _ = buff.read_u8()?;
- let _ = buff.read_u8()?;
+ // let _ = buff.read_u8()?;
+ // let _ = buff.read_u8()?;
// visible flags
- let _ = buff.read_i64()?;
- // "overriden by subclasses"
- self.block.read(buff, reg, map)?;
- Ok(())
+ // let _ = buff.read_i64()?;
+ // implementation not complete, simply error, causing the remaining bytes in the chunk to be skipped (TODO finish impl)
+ Err(ReadError::Version(0x0))
+ // "overridden by subclasses"
+ // self.block.read(buff, reg, map)?;
+ // Ok(())
}
}
-/// a map
+/// a map.
+/// ## Does not support serialization yet!
#[derive(Debug)]
pub struct Map<'l> {
- pub width: u32,
- pub height: u32,
+ pub width: usize,
+ pub height: usize,
pub tags: HashMap<String, String>,
+ /// row major 2d array
+ /// ```rs
+ /// (0, 0), (1, 0), (2, 0)
+ /// (0, 1), (1, 1), (2, 1)
+ /// (0, 2), (1, 2), (2, 2)
+ /// ```
pub tiles: Vec<Tile<'l>>,
}
+macro_rules! cond {
+ ($cond: expr, $do: expr) => {
+ if $cond {
+ None
+ } else {
+ $do
+ }
+ };
+}
+
+impl<'l> Crossable for Map<'l> {
+ // N
+ // cond!(pos.position.1 >= (pos.height - 1) as u16, get(j + 1)),
+ // // E
+ // cond!(
+ // pos.position.0 >= (pos.height - 1) as u16,
+ // get(j + pos.height)
+ // ),
+ // // S
+ // cond!(
+ // pos.position.1 == 0 || pos.position.1 >= pos.height as u16,
+ // cond!(pos.position.1 >= (pos.height - 1), get(j + 1)),
+ // // E
+ // cond!(pos.position.0 >= (pos.height - 1), get(j + pos.height)),
+ // // S
+ // cond!(
+ // pos.position.1 == 0 || pos.position.1 >= pos.height,
+ // get(j - 1)
+ // ),
+ // // W
+ // cond!(j < pos.height, get(j - pos.height)),
+ fn cross(&self, j: usize, c: &PositionContext) -> Cross {
+ let get = |i| {
+ let b = &self[i];
+ Some((b.get_block()?, b.get_rotation()?))
+ };
+ [
+ cond![
+ c.position.1 == 0 || c.position.1 >= c.height,
+ get(j + self.height)
+ ],
+ cond![c.position.0 >= (c.height - 1), get(j + 1)],
+ cond![c.position.1 >= (c.height - 1), get(j - self.width)],
+ cond![j < c.height, get(j - 1)],
+ ]
+ }
+}
+
+impl<'l> Map<'l> {
+ pub fn new(width: usize, height: usize, tags: HashMap<String, String>) -> Self {
+ Self {
+ tiles: vec![Tile::new(&environment::STONE, None); width * height],
+ height,
+ width,
+ tags,
+ }
+ }
+}
+
+impl<'l> Index<usize> for Map<'l> {
+ type Output = Tile<'l>;
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.tiles[index]
+ }
+}
+
+impl<'l> IndexMut<usize> for Map<'l> {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ &mut self.tiles[index]
+ }
+}
+
const MAP_HEADER: [u8; 4] = [b'M', b'S', b'A', b'V'];
-/// error ocurring when reading a map fails
+/// error occurring when reading a map fails
#[derive(Debug, Error)]
pub enum ReadError {
#[error("failed to read from buffer")]
Read(#[from] super::ReadError),
+ #[error(transparent)]
+ Decompress(#[from] super::DecompressError),
#[error("incorrect header ({0:?})")]
Header([u8; 4]),
#[error("unsupported version ({0})")]
@@ -286,7 +405,7 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> {
return Err(ReadError::Version(version.try_into().unwrap_or(0)));
}
let mut tags = HashMap::new();
- buff.read_chunk(|buff| {
+ buff.read_chunk(true, |buff| {
buff.skip(1)?;
for _ in 0..buff.read_u8()? {
let key = buff.read_utf()?;
@@ -295,71 +414,40 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> {
}
Ok::<(), super::ReadError>(())
})?;
- buff.read_chunk(|buff| {
- // we skip these (just keep the respective modules updated)
- for _ in 0..buff.read_i8()? {
- // let _ty = buff.read_u8()?;
- // for _ in 0..buff.read_i16()? {
- // let name = dbg!(buff.read_utf()?);
- // }
- buff.skip(1)?;
- for _ in 0..buff.read_u16()? {
- let n = buff.read_u16()?;
- buff.skip(n as usize)?;
- }
- }
- Ok::<(), super::ReadError>(())
- })?;
-
- // map section
+ // we skip the content header (just keep the respective modules updated)
+ buff.skip_chunk()?;
+ // map section
let mut w = 0;
let mut h = 0;
- let mut tiles = vec![];
- buff.read_chunk(|buff| {
- w = buff.read_u16()? as u32;
- h = buff.read_u16()? as u32;
+ let mut m = None;
+ buff.read_chunk(true, |buff| {
+ w = buff.read_u16()? as usize;
+ h = buff.read_u16()? as usize;
+ let mut map = Map::new(w, h, tags);
let count = w * h;
let mut i = 0;
while i < count {
- let x = (i % w) as u16;
- let y = (i / w) as u16;
let floor_id = buff.read_u16()?;
let overlay_id = buff.read_u16()?;
let floor = BlockEnum::try_from(floor_id)
.unwrap_or(BlockEnum::Stone)
.to(self.0)
- .unwrap_or(&crate::block::environment::STONE);
+ .unwrap_or(&environment::STONE);
let ore = BlockEnum::try_from(overlay_id)
.unwrap_or(BlockEnum::Air)
.to(self.0);
- debug_assert!(
- x < w as u16 && y < h as u16,
- "{x} or {y} out of bounds ({floor:?} {ore:?})"
- );
- tiles.push(Tile {
- floor,
- ore,
- pos: GridPos(x, y),
- build: None,
- });
- let consecutives = buff.read_u8()? as u32;
+ map[i] = Tile::new(floor, ore);
+ let consecutives = buff.read_u8()? as usize;
if consecutives > 0 {
for i in (i + 1)..(i + 1 + consecutives) {
- let x = (i % w) as u16;
- let y = (i / w) as u16;
- tiles.push(Tile {
- floor,
- ore,
- pos: GridPos(x, y),
- build: None,
- })
+ map[i] = Tile::new(floor, ore)
}
i += consecutives;
}
i += 1;
}
let mut i = 0usize;
- while i < count as usize {
+ while i < count {
let block_id = buff.read_u16()?;
let packed = buff.read_u8()?;
let entity = (packed & 1) != 0;
@@ -378,30 +466,30 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> {
};
if central {
if let Some(block) = block {
- tiles[i].set_block(block);
+ map[i].set_block(block);
}
}
if entity {
if central {
- // TODO: actually read
- let n = buff.read_u16()?;
- buff.skip(n as usize)?;
- // let _ = buff.read_i8()?;
- // tiles[i]
- // .build
- // .as_mut()
- // .unwrap()
- // // map not initialized yet
- // .read(&mut buff, self.0, &HashMap::new())?;
+ let _ = buff.read_chunk(false, |buff| {
+ let _ = buff.read_i8()?;
+ map[i]
+ .build
+ .as_mut()
+ .unwrap()
+ // map not initialized yet
+ .read(buff, self.0, &HashMap::new())?;
+ Ok::<(), ReadError>(())
+ });
}
} else if data {
if let Some(block) = block {
- tiles[i].set_block(block);
+ map[i].set_block(block);
}
- tiles[i].build.as_mut().unwrap().data = buff.read_i8()?;
+ map[i].build.as_mut().unwrap().data = buff.read_i8()?;
} else {
let consecutives = buff.read_u8()? as usize;
- for tile in tiles.iter_mut().take(consecutives).skip(i + 1) {
+ for tile in map.tiles.iter_mut().take(consecutives).skip(i + 1) {
if let Some(block) = block {
tile.set_block(block);
}
@@ -410,10 +498,11 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> {
}
i += 1
}
+ m = Some(map);
Ok::<(), ReadError>(())
})?;
let mut mapping = EntityMapping::new();
- buff.read_chunk(|buff| {
+ buff.read_chunk(true, |buff| {
for _ in 0..buff.read_u16()? {
let id = buff.read_i16()? as u8;
let nam = buff.read_utf()?;
@@ -442,12 +531,7 @@ impl<'l> Serializer<Map<'l>> for MapSerializer<'l> {
})?;
// skip custom chunks
buff.skip_chunk()?;
- Ok(Map {
- width: w,
- height: h,
- tags,
- tiles,
- })
+ Ok(m.unwrap())
}
/// serialize a map (todo)
diff --git a/src/data/mod.rs b/src/data/mod.rs
index 589a33c..c658210 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -1,7 +1,7 @@
//! all the IO
use flate2::{
- Compress, CompressError, Compression, Decompress, DecompressError, FlushCompress,
- FlushDecompress, Status,
+ Compress, CompressError as CError, Compression, Decompress, DecompressError as DError,
+ FlushCompress, FlushDecompress, Status,
};
use std::collections::HashMap;
use std::error::Error;
@@ -124,16 +124,28 @@ impl<'d> DataRead<'d> {
Ok(len)
}
- pub fn read_chunk<E>(&mut self, f: impl FnOnce(&mut DataRead) -> Result<(), E>) -> Result<(), E>
+ pub fn read_chunk<E>(
+ &mut self,
+ big: bool,
+ f: impl FnOnce(&mut DataRead) -> Result<(), E>,
+ ) -> Result<(), E>
where
E: Error + From<ReadError>,
{
- let len = self.read_u32()? as usize;
+ let len = if big {
+ self.read_u32()? as usize
+ } else {
+ self.read_u16()? as usize
+ };
self.read = 0;
let r = f(self);
match r {
Err(e) => {
// skip this chunk
+ if len < self.read {
+ eprintln!("overread ({e:?})");
+ return Err(e);
+ }
let n = len - self.read;
if n != 0 {
self.skip(n)?;
@@ -167,7 +179,7 @@ impl<'d> DataRead<'d> {
Ok(())
}
- pub fn deflate(&mut self) -> Result<Vec<u8>, ReadError> {
+ pub fn deflate(&mut self) -> Result<Vec<u8>, DecompressError> {
let mut dec = Decompress::new(true);
let mut raw = Vec::<u8>::new();
raw.reserve(1024);
@@ -187,7 +199,7 @@ impl<'d> DataRead<'d> {
}
if dec.total_in() == t_in && dec.total_out() == t_out {
// protect against looping forever
- return Err(ReadError::DecompressStall);
+ return Err(DecompressError::DecompressStall);
}
raw.reserve(1024);
}
@@ -198,11 +210,15 @@ impl<'d> DataRead<'d> {
}
#[derive(Debug, Error)]
-pub enum ReadError {
+pub enum DecompressError {
+ #[error("zlib decompression failed")]
+ Decompress(#[from] DError),
#[error("decompressor stalled before completion")]
DecompressStall,
- #[error("zlib decompession failed")]
- Decompress(#[from] DecompressError),
+}
+
+#[derive(Debug, Error)]
+pub enum ReadError {
#[error("buffer underflow (expected {need} but got {have})")]
Underflow { need: usize, have: usize },
#[error("expected {0}")]
@@ -309,7 +325,17 @@ impl<'d> DataWrite<'d> {
}
}
- pub fn inflate(self, to: &mut DataWrite) -> Result<(), WriteError> {
+ /// eat this datawrite
+ ///
+ /// panics if ref write buffer
+ pub fn consume(self) -> Vec<u8> {
+ match self.data {
+ WriteBuff::Vec(v) => v,
+ WriteBuff::Ref { .. } => unreachable!(),
+ }
+ }
+
+ pub fn inflate(self, to: &mut DataWrite) -> Result<(), CompressError> {
// compress into the provided buffer
let WriteBuff::Vec(raw) = self.data else {
unreachable!("write buffer not owned")
@@ -324,7 +350,7 @@ impl<'d> DataWrite<'d> {
match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)? {
// there's no more input (and the flush mode says so), but we can't resize the output
Status::Ok | Status::BufError => {
- return Err(WriteError::CompressEof(
+ return Err(CompressError::CompressEof(
raw.len() - comp.total_in() as usize,
))
}
@@ -350,7 +376,7 @@ impl<'d> DataWrite<'d> {
}
if comp.total_in() == t_in && comp.total_out() == t_out {
// protect against looping forever
- return Err(WriteError::CompressStall);
+ return Err(CompressError::CompressStall);
}
dst.reserve(1024);
}
@@ -370,20 +396,21 @@ impl Default for DataWrite<'static> {
}
#[derive(Debug, Error)]
+pub enum CompressError {
+ #[error(transparent)]
+ Compress(#[from] CError),
+ #[error("compression overflow with {0} bytes of input remaining")]
+ CompressEof(usize),
+ #[error("compressor stalled before completion")]
+ CompressStall,
+}
+
+#[derive(Debug, Error)]
pub enum WriteError {
#[error("buffer overflow (expected {need} but got {have})")]
Overflow { need: usize, have: usize },
#[error("string too long ({len} bytes of {})", u16::MAX)]
TooLong { len: usize },
- #[error("zlib compression failed")]
- Compress {
- #[from]
- source: CompressError,
- },
- #[error("compression overflow with {0} bytes of input remaining")]
- CompressEof(usize),
- #[error("compressor stalled before completion")]
- CompressStall,
}
impl PartialEq for WriteError {
@@ -428,18 +455,28 @@ pub trait Serializer<D> {
fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &D) -> Result<(), Self::WriteError>;
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct GridPos(pub u16, pub u16);
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct GridPos(pub usize, pub usize);
impl From<u32> for GridPos {
fn from(value: u32) -> Self {
- GridPos((value >> 16) as u16, value as u16)
+ GridPos((value >> 16) as u16 as usize, value as u16 as usize)
}
}
impl From<GridPos> for u32 {
+ /// ```
+ /// # use mindus::data::GridPos;
+ /// assert_eq!(GridPos::from(u32::from(GridPos(1000, 5))), GridPos(1000, 5));
+ /// ```
fn from(value: GridPos) -> Self {
- (u32::from(value.0) << 16) | u32::from(value.1)
+ ((value.0 << 16) | value.1) as u32
+ }
+}
+
+impl fmt::Debug for GridPos {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "({0}, {1})", self.0, self.1)
}
}
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index a1af661..0454d3c 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -2,20 +2,21 @@
use dashmap::mapref::one::Ref;
use dashmap::DashMap;
use image::codecs::png::PngDecoder;
-use image::{DynamicImage, RgbaImage};
+pub(crate) use image::{DynamicImage, RgbaImage};
use std::io::{BufReader, Cursor};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use zip::ZipArchive;
use crate::block::environment::METAL_FLOOR;
-use crate::data::map::Tile;
+use crate::block::{Block, Rotation};
use crate::team::SHARDED;
-use crate::utils::ImageUtils;
+pub(crate) use crate::utils::ImageUtils;
use crate::Map;
-pub use std::borrow::Borrow;
+pub(crate) use std::borrow::{Borrow, BorrowMut};
use super::schematic::Schematic;
+use super::GridPos;
type Cache = DashMap<PathBuf, RgbaImage>;
fn cache() -> &'static Cache {
@@ -45,6 +46,18 @@ impl Borrow<RgbaImage> for ImageHolder {
}
}
+impl BorrowMut<RgbaImage> for ImageHolder {
+ fn borrow_mut(&mut self) -> &mut RgbaImage {
+ match self {
+ Self::Own(x) => x,
+ Self::Borrow(_) => {
+ *self = Self::from(std::mem::replace(self, Self::from(RgbaImage::new(0, 0))).own());
+ self.borrow_mut()
+ }
+ }
+ }
+}
+
impl From<Option<Ref<'static, PathBuf, RgbaImage>>> for ImageHolder {
fn from(value: Option<Ref<'static, PathBuf, RgbaImage>>) -> Self {
Self::Borrow(value.unwrap())
@@ -63,6 +76,61 @@ impl From<RgbaImage> for ImageHolder {
}
}
+pub type Cross<'l> = [Option<(&'l Block, Rotation)>; 4];
+/// holds the 4 bordering blocks
+#[derive(Copy, Clone)]
+pub struct RenderingContext<'l> {
+ pub cross: Cross<'l>,
+ pub rotation: Rotation,
+ pub position: PositionContext,
+}
+
+/// holds positions
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct PositionContext {
+ pub position: GridPos,
+ pub width: usize,
+ pub height: usize,
+}
+
+impl std::fmt::Debug for PositionContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "PC<{:?} ({}/{})>",
+ self.position, self.width, self.height
+ )
+ }
+}
+
+impl std::fmt::Debug for RenderingContext<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self, f)
+ }
+}
+
+impl std::fmt::Display for RenderingContext<'_> {
+ /// this display impl shows RC<$directions=+own rotation>
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "RC<")?;
+ macro_rules! f {
+ ($f:expr, $z:expr, $x:literal, $at: expr, $srot: expr) => {
+ if let Some((_, rot)) = $z {
+ if (rot == $at && rot.mirrored(true, true) != $srot) {
+ $f.write_str($x)?;
+ }
+ }
+ };
+ }
+ f!(f, self.cross[0], "N = ", Rotation::Down, self.rotation);
+ f!(f, self.cross[1], "E = ", Rotation::Left, self.rotation);
+ f!(f, self.cross[2], "S = ", Rotation::Up, self.rotation);
+ f!(f, self.cross[3], "W = ", Rotation::Right, self.rotation);
+
+ write!(f, "{:?}>", self.rotation)
+ }
+}
+
static CACHE: OnceLock<Cache> = OnceLock::new();
pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, RgbaImage>> {
let key = Path::new("blocks").join(category).join(name);
@@ -83,7 +151,10 @@ pub(crate) fn load(category: &str, name: &str) -> Option<Ref<'static, PathBuf, R
fn load_raw(f: impl AsRef<Path>) -> Option<RgbaImage> {
let f = std::fs::File::open(Path::new("target/out").join(f)).ok()?;
let r = PngDecoder::new(BufReader::new(f)).unwrap();
- Some(DynamicImage::from_decoder(r).unwrap().into_rgba8())
+ let p = DynamicImage::from_decoder(r).unwrap().into_rgba8();
+ assert!(p.width() != 0);
+ assert!(p.height() != 0);
+ Some(p)
}
fn load_zip() {
@@ -128,61 +199,271 @@ where
c
}
-/// renderer for creating images of schematics
-pub struct Renderer {}
-impl<'l> Renderer {
- /// creates a picture of a schematic. Bridges and node connections are not drawn, and there is no background.
- /// conveyors, conduits, and ducts currently do not render.
+pub trait RotationState {
+ fn get_rotation(&self) -> Option<Rotation>;
+}
+pub trait BlockState<'l> {
+ fn get_block(&'l self) -> Option<&'l Block>;
+}
+pub(crate) trait Crossable {
+ fn cross(&self, j: usize, c: &PositionContext) -> Cross;
+}
+
+// pub(crate) trait Darray {
+// type Output;
+// fn n(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
+// fn e(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
+// fn s(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
+// fn w(&self, j: usize, c: &PositionContext) -> Option<&Self::Output>;
+// }
+
+#[cfg(test)]
+fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String {
+ let mut s = String::new();
+ for c in v.chunks(height) {
+ for c in c {
+ s.push(c[0].map_or('_', |(_, r)| r.ch()));
+ for c in c[1..].iter() {
+ s.push(',');
+ s.push(c.map_or('_', |(_, r)| r.ch()));
+ }
+ s.push(' ');
+ }
+ s.push('\n');
+ }
+ s
+}
+
+#[test]
+fn test_cross() {
+ use crate::block::distribution::define;
+ let mut reg = crate::block::BlockRegistry::default();
+ crate::block::distribution::register(&mut reg);
+ let mut ss = super::schematic::SchematicSerializer(&reg);
+ macro_rules! test {
+ ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => {
+ let s = ss.deserialize_base64($schem).unwrap();
+ let mut c = vec![];
+ println!("{:#?}", s.blocks);
+
+ for (position, _) in s.block_iter() {
+ let pctx = PositionContext {
+ position,
+ width: s.width,
+ height: s.height,
+ };
+ c.push(s.cross(&pctx));
+ }
+ let n = s.tags.get("name").map_or("<unknown>", |x| &x);
+ let cc: Vec<Cross> = vec![
+ $(define!($a,$b,$c,$d),)*
+ ];
+ if cc != c {
+ let a = print_crosses(cc, s.height as usize);
+ let b = print_crosses(c, s.height as usize);
+ for diff in diff::lines(&a, &b) {
+ match diff {
+ diff::Result::Left(l) => println!("\x1b[38;5;1m{}", l),
+ diff::Result::Right(r) => println!("\x1b[38;5;2m{}", r),
+ diff::Result::Both(l, _) => println!("\x1b[0m{}", l),
+ }
+ }
+ print!("\x1b[0m");
+ /*
+ for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) {
+ match diff {
+ diff::Result::Left((i, l)) => println!("\x1b[38;5;1m- {l:?} at {i}"),
+ diff::Result::Right((i, r)) => println!("\x1b[38;5;2m+ {r:?} at {i}"),
+ diff::Result::Both((i, l), _) => println!("\x1b[0m {l:?} at {i}"),
+ }
+ }
+ */
+ panic!("test {n} \x1b[38;5;1mfailed\x1b[0m")
+ }
+ println!("test {n} \x1b[38;5;2mpassed\x1b[0m");
+ };
+ }
+ // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1...
+ // the symbols are directions (> => Right...), which mean the neighbors pointing direction
+ // _ = no block
+
+ // the basic test
+ // ─┐
+ // ─┤
+ test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" =>
+ // (0, 0) (0, 1)
+ // n e s w borders (west void for first row)
+ >,v,_,_ _,v,>,_
+ // (1, 0) (1, 1)
+ v,_,_,> _,_,v,>
+ );
+ // the loop test
+ // ─│─
+ // ─┼┐
+ // ─└┘
+ test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" =>
+ >,^,_,_ <,>,<,_ _,v,>,_
+ >,<,_,< v,v,^,> _,>,>,<
+ v,_,_,^ >,_,<,> _,_,v,v
+ );
+ // the snek test
+ // └┐
+ // ─┘
+ test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" =>
+ ^,^,_,_ _,<,>,_
+ <,_,_,> _,_,^,^
+ );
+
+ // the notile test
+ test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" =>
+ <,>,_,_ _,^,v,_
+ ^,_,_,v _,_,>,<
+ );
+ // the asymmetrical test
+ // <───
+ // ───>
+ test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" =>
+ <,>,_,_ _,<,>,_
+ <,>,_,> _,<,>,<
+ // <,_,_,> _,_,>,<
+ <,_,_,> _,_,>,<
+ );
+
+ // the complex test
+ // ─┬─│││─
+ // ─┤─┘─┘─
+ // ─┤┌─│─┐
+ // ─┼┘─┴─│
+ test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" =>
+ >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_
+ v,<,_,> v,v,v,> v,>,v,> _,<,v,>
+ v,>,_,v >,<,<,v <,^,v,v _,v,>,v
+ <,>,_,< ^,v,>,v v,>,<,> _,^,^,<
+ v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v
+ >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^
+ // <,_,_,< ^,_,>,v v,_,<,> _,_,^,<
+ // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v
+ // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^
+ v,_,_,< >,_,v,> >,_,v,^ _,_,>,^
+ );
+}
+
+/// trait for renderable objects
+pub trait Renderable {
+ /// creates a picture of a schematic. Bridges and node connections are not drawn.
+ fn render(&self) -> RgbaImage;
+}
+
+impl Renderable for Schematic<'_> {
/// ```
/// use mindus::*;
/// let mut s = Schematic::new(2, 3);
/// s.put(0, 0, &block::distribution::DISTRIBUTOR);
- /// s.put(0, 3, &block::distribution::ROUTER);
- /// s.put(1, 3, &block::walls::COPPER_WALL);
- /// let output /*: RgbaImage */ = Renderer::render_schematic(&s);
+ /// s.put(0, 2, &block::distribution::ROUTER);
+ /// s.put(1, 2, &block::walls::COPPER_WALL);
+ /// let output /*: RgbaImage */ = s.render();
/// ```
- pub fn render_schematic(s: &'l Schematic<'_>) -> RgbaImage {
+ fn render(&self) -> RgbaImage {
+ dbg!(&self.blocks.clone());
load_zip();
- let mut canvas = RgbaImage::new((s.width * 32).into(), (s.height * 32).into());
// fill background
- canvas.repeat(METAL_FLOOR.image(None).borrow());
- for tile in s.block_iter() {
- let x = (tile.pos.0 - ((tile.block.get_size() - 1) / 2) as u16) as u32;
- let y = (s.height - tile.pos.1 - ((tile.block.get_size() / 2) + 1) as u16) as u32;
- canvas.overlay(tile.image().borrow(), x * 32, y * 32);
+ let mut bg = RgbaImage::new(
+ ((self.width + 2) * 32) as u32,
+ ((self.height + 2) * 32) as u32,
+ );
+ bg.repeat(METAL_FLOOR.image(None, None).borrow());
+ let mut canvas = RgbaImage::new(
+ ((self.width + 2) * 32) as u32,
+ ((self.height + 2) * 32) as u32,
+ );
+ for (GridPos(x, y), tile) in self.block_iter() {
+ let ctx = if tile.block.wants_context() {
+ let pctx = PositionContext {
+ position: GridPos(x, y),
+ width: self.width,
+ height: self.height,
+ };
+ Some(RenderingContext {
+ cross: self.cross(&pctx),
+ rotation: tile.rot,
+ position: pctx,
+ })
+ } else {
+ None
+ };
+ #[cfg(debug_assertions)]
+ println!("rendering {tile:?} ({x}, {y}) [+{}]", tile.block.get_size());
+ let x = x as u32 - ((tile.block.get_size() - 1) / 2) as u32;
+ let y = self.height as u32 - y as u32 - ((tile.block.get_size() / 2) + 1) as u32;
+ canvas.overlay(
+ tile.image(ctx.as_ref()).borrow(),
+ (x + 1) * 32,
+ (y + 1) * 32,
+ );
+ // canvas.save("tmp.png").unwrap();
}
- canvas
+ #[cfg(debug_assertions)]
+ println!("finishing up");
+ image::imageops::overlay(&mut bg, canvas.shadow(), 0, 0);
+ bg
}
+}
- pub fn render_map(m: &'l Map<'_>) -> RgbaImage {
+impl Renderable for Map<'_> {
+ fn render(&self) -> RgbaImage {
load_zip();
- let mut canvas = RgbaImage::new(m.width * 8, m.height * 8);
- const VEC: Vec<&Tile<'_>> = vec![];
- let mut layers = [VEC; 2];
- for tile in m.tiles.iter() {
- if tile.has_building() {
- layers[1].push(tile)
+ let mut floor = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8);
+ let mut top = RgbaImage::new(self.width as u32 * 8, self.height as u32 * 8);
+ for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| {
+ (
+ (j % self.width),
+ // flip y
+ (self.height - (j / self.width)) - 1,
+ j,
+ t,
+ )
+ }) {
+ if tile.build().is_none() {
+ floor.overlay(
+ // SAFETY: [`load_raw`] forces nonzero image size
+ unsafe { &tile.image(None).own().scale(8) },
+ x as u32 * 8,
+ y as u32 * 8,
+ );
} else {
- layers[0].push(tile)
- }
- }
- for tiles in layers {
- for tile in tiles {
- let s = if let Some(build) = &tile.build {
+ let s = if let Some(build) = &tile.build() {
build.block.get_size()
} else {
1
};
- let x = (tile.pos.0 - ((s - 1) / 2) as u16) as u32;
- let y = (m.height as u16 - tile.pos.1 - ((s / 2) + 1) as u16) as u32;
- canvas.overlay(
- // SAFETY: surely not 0. (tile.size can never be 0). im not sure if you can load a 0 sized image.. but you might be able to.
- unsafe { &tile.image().own().scale(tile.size() as u32 * 8) },
- x * 8,
- y * 8,
+ let x = x - ((s - 1) / 2) as usize;
+ let y = y - (s / 2) as usize;
+ let ctx = (|| {
+ let b = tile.build()?;
+ if !b.block.wants_context() {
+ return None;
+ }
+ let pctx = PositionContext {
+ position: GridPos(x, y),
+ width: self.width,
+ height: self.height,
+ };
+ let rctx = RenderingContext {
+ cross: self.cross(j, &pctx),
+ rotation: b.rotation,
+ position: pctx,
+ };
+ Some(rctx)
+ })();
+ top.overlay(
+ // SAFETY: tile.size can never be 0, and [`load_raw`] forces nonzero.
+ unsafe { &tile.image(ctx.as_ref()).own().scale(tile.size() as u32 * 8) },
+ x as u32 * 8,
+ y as u32 * 8,
);
}
}
- canvas
+ image::imageops::overlay(&mut floor, top.shadow(), 0, 0);
+ floor
}
}
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 75378ba..2a71b3e 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -2,25 +2,24 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::{self, Write};
-use std::iter::FusedIterator;
-use std::slice::Iter;
use thiserror::Error;
use crate::block::{self, Block, BlockRegistry, Rotation, State};
use crate::data::base64;
use crate::data::dynamic::{self, DynData, DynSerializer};
+use crate::data::renderer::*;
use crate::data::{self, DataRead, DataWrite, GridPos, Serializer};
use crate::item::storage::ItemStorage;
use crate::registry::RegistryEntry;
+use crate::utils::array::Array2D;
/// biggest schematic
-pub const MAX_DIMENSION: u16 = 256;
+pub const MAX_DIMENSION: usize = 256;
/// most possible blocks
pub const MAX_BLOCKS: u32 = 256 * 256;
/// a placement in a schematic
pub struct Placement<'l> {
- pub pos: GridPos,
pub block: &'l Block,
pub rot: Rotation,
state: Option<State>,
@@ -28,11 +27,26 @@ pub struct Placement<'l> {
impl PartialEq for Placement<'_> {
fn eq(&self, rhs: &Placement<'_>) -> bool {
- self.pos == rhs.pos && self.block == rhs.block && self.rot == rhs.rot
+ self.block == rhs.block && self.rot == rhs.rot
+ }
+}
+
+impl fmt::Debug for Placement<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ write!(f, "P<{}[*{}]>", self.block.name(), self.rot.ch())
}
}
impl<'l> Placement<'l> {
+ /// make a placement from a block
+ pub fn new(block: &'l Block) -> Self {
+ Self {
+ block,
+ rot: Rotation::Up,
+ state: None,
+ }
+ }
+
/// gets the current state of this placement. you can cast it with `placement.block::get_state(placement.get_state()?)?`
#[must_use]
pub fn get_state(&self) -> Option<&State> {
@@ -45,8 +59,8 @@ impl<'l> Placement<'l> {
}
/// draws this placement in particular
- pub fn image(&self) -> crate::data::renderer::ImageHolder {
- self.block.image(self.get_state())
+ pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder {
+ self.block.image(self.get_state(), context)
}
/// set the state
@@ -61,11 +75,40 @@ impl<'l> Placement<'l> {
}
}
+impl<'l> BlockState<'l> for Placement<'l> {
+ fn get_block(&self) -> Option<&'l Block> {
+ Some(self.block)
+ }
+}
+
+impl RotationState for Placement<'_> {
+ fn get_rotation(&self) -> Option<Rotation> {
+ Some(self.rot)
+ }
+}
+
+impl<'l> BlockState<'l> for Option<Placement<'l>> {
+ fn get_block(&self) -> Option<&'l Block> {
+ let Some(p) = self else {
+ return None;
+ };
+ Some(p.block)
+ }
+}
+
+impl RotationState for Option<Placement<'_>> {
+ fn get_rotation(&self) -> Option<Rotation> {
+ let Some(p) = self else {
+ return None;
+ };
+ Some(p.rot)
+ }
+}
+
// manual impl because trait objects cannot be cloned
impl<'l> Clone for Placement<'l> {
fn clone(&self) -> Self {
Self {
- pos: self.pos,
block: self.block,
state: match self.state {
None => None,
@@ -76,14 +119,14 @@ impl<'l> Clone for Placement<'l> {
}
}
-#[derive(Clone)]
+#[derive(Clone, Debug)]
/// a schematic.
pub struct Schematic<'l> {
- pub width: u16,
- pub height: u16,
+ pub width: usize,
+ pub height: usize,
pub tags: HashMap<String, String>,
- pub blocks: Vec<Placement<'l>>,
- lookup: Vec<Option<usize>>,
+ /// schems can have holes, so [Option] is used.
+ pub blocks: Array2D<Option<Placement<'l>>>,
}
impl<'l> PartialEq for Schematic<'l> {
@@ -102,7 +145,7 @@ impl<'l> Schematic<'l> {
/// # use mindus::Schematic;
/// let s = Schematic::new(5, 5);
/// ```
- pub fn new(width: u16, height: u16) -> Self {
+ pub fn new(width: usize, height: usize) -> Self {
match Self::try_new(width, height) {
Ok(s) => s,
Err(NewError::Width(w)) => panic!("invalid schematic width ({w})"),
@@ -110,12 +153,38 @@ impl<'l> Schematic<'l> {
}
}
+ /// the area around a point
+ pub(crate) fn cross(&self, c: &PositionContext) -> Cross {
+ let get = |x, y| {
+ let b = self.get(x?, y?).ok()??;
+ Some((b.get_block()?, b.get_rotation()?))
+ };
+ macro_rules! s {
+ ($x:expr) => {
+ Some($x)
+ };
+ ($a:expr => $b:expr) => {
+ if $a < $b {
+ None
+ } else {
+ Some($a - $b)
+ }
+ };
+ }
+ [
+ get(s!(c.position.0), s!(c.position.1 + 1)),
+ get(s!(c.position.0 + 1), s!(c.position.1)),
+ get(s!(c.position.0), s!(c.position.1 => 1)),
+ get(s!(c.position.0 => 1), s!(c.position.1)),
+ ]
+ }
+
/// create a new schematic, erroring if too big
/// ```
/// # use mindus::Schematic;
/// assert!(Schematic::try_new(500, 500).is_err() == true);
/// ```
- pub fn try_new(width: u16, height: u16) -> Result<Self, NewError> {
+ pub fn try_new(width: usize, height: usize) -> Result<Self, NewError> {
if width > MAX_DIMENSION {
return Err(NewError::Width(width));
}
@@ -130,66 +199,52 @@ impl<'l> Schematic<'l> {
width,
height,
tags,
- blocks: Vec::new(),
- lookup: Vec::new(),
+ blocks: Array2D::new(None, width, height),
})
}
- #[must_use]
- /// have blocks?
- pub fn is_empty(&self) -> bool {
- self.blocks.is_empty()
- }
+ // #[must_use]
+ // /// check if a rect is empty
+ // /// ```
+ // /// # use mindus::Schematic;
+ // /// # use mindus::block::distribution::ROUTER;
+ // /// let mut s = Schematic::new(5, 5);
+ // /// s.put(0, 0, &ROUTER);
+ // /// assert!(s.is_region_empty(1, 1, 4, 4));
+ // /// s.put(2, 2, &ROUTER);
+ // /// assert!(s.is_region_empty(1, 1, 4, 4) == false);
+ // /// // out of bounds is empty
+ // /// assert!(s.is_region_empty(25, 25, 0, 0));
+ // /// ```
+ // pub fn is_region_empty(&self, x: usize, y: usize, w: usize, h: usize) -> bool {
+ // if x >= self.width || y >= self.height || w == 0 || h == 0 {
+ // return true;
+ // }
+ // if w > 1 || h > 1 {
+ // for y in y..(y + h).min(self.height) {
+ // for x in x..(x + w).min(self.width) {
+ // if self.get(x, y).unwrap().is_some() {
+ // return false;
+ // }
+ // }
+ // }
+ // true
+ // } else {
+ // self.get(x, y).unwrap().is_none()
+ // }
+ // }
- #[must_use]
- /// count blocks
- pub fn get_block_count(&self) -> usize {
- self.blocks.len()
- }
-
- #[must_use]
- /// check if a rect is empty
+ /// gets a block
/// ```
/// # use mindus::Schematic;
- /// let s = Schematic::new(5, 5);
- /// assert!(s.is_region_empty(1, 1, 4, 4) == true);
+ /// # use mindus::block::Rotation;
+ ///
+ /// let mut s = Schematic::new(5, 5);
+ /// assert!(s.get(0, 0).unwrap().is_none());
+ /// s.put(0, 0, &mindus::block::turrets::DUO);
+ /// assert!(s.get(0, 0).unwrap().is_some());
/// ```
- pub fn is_region_empty(&self, x: u16, y: u16, w: u16, h: u16) -> bool {
- if self.blocks.is_empty() {
- return true;
- }
- if x >= self.width || y >= self.height || w == 0 || h == 0 {
- return true;
- }
- if w > 1 || h > 1 {
- let stride = self.width as usize;
- let x_end = if self.width - x > w {
- x + w
- } else {
- self.width
- } as usize;
- let y_end = if self.height - y > h {
- y + h
- } else {
- self.height
- } as usize;
- let x = x as usize;
- let y = y as usize;
- for cy in y..y_end {
- for cx in x..x_end {
- if self.lookup[cx + cy * stride].is_some() {
- return false;
- }
- }
- }
- true
- } else {
- self.lookup[(x as usize) + (y as usize) * (self.width as usize)].is_none()
- }
- }
-
- /// gets a block
- pub fn get(&self, x: u16, y: u16) -> Result<Option<&Placement<'l>>, PosError> {
+ pub fn get(&self, x: usize, y: usize) -> Result<Option<&Placement<'l>>, PosError> {
if x >= self.width || y >= self.height {
return Err(PosError {
x,
@@ -198,18 +253,15 @@ impl<'l> Schematic<'l> {
h: self.height,
});
}
- if self.blocks.is_empty() {
- return Ok(None);
- }
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos] {
- None => Ok(None),
- Some(idx) => Ok(Some(&self.blocks[idx])),
+ let b = &self.blocks[x][y];
+ match b {
+ Some(b) => Ok(Some(b)),
+ _ => Ok(None),
}
}
/// gets a block, mutably
- pub fn get_mut(&mut self, x: u16, y: u16) -> Result<Option<&mut Placement<'l>>, PosError> {
+ pub fn get_mut(&mut self, x: usize, y: usize) -> Result<Option<&mut Placement<'l>>, PosError> {
if x >= self.width || y >= self.height {
return Err(PosError {
x,
@@ -218,68 +270,24 @@ impl<'l> Schematic<'l> {
h: self.height,
});
}
- if self.blocks.is_empty() {
- return Ok(None);
- }
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos] {
- None => Ok(None),
- Some(idx) => Ok(Some(&mut self.blocks[idx])),
+ let b = &mut self.blocks[x][y];
+ match b {
+ Some(b) => Ok(Some(b)),
+ _ => Ok(None),
}
}
- fn remove(&mut self, idx: usize) -> Placement<'l> {
- // swap_remove not only avoids moves in self.blocks but also reduces the lookup changes we have to do
- let prev = self.blocks.swap_remove(idx);
- self.fill_lookup(
- prev.pos.0 as usize,
- prev.pos.1 as usize,
- prev.block.get_size() as usize,
- None,
- );
- if idx < self.blocks.len() {
- // fix the swapped block's lookup entries
- let swapped = &self.blocks[idx];
- self.fill_lookup(
- swapped.pos.0 as usize,
- swapped.pos.1 as usize,
- swapped.block.get_size() as usize,
- Some(idx),
- );
- }
- prev
- }
-
- fn fill_lookup(&mut self, x: usize, y: usize, sz: usize, val: Option<usize>) {
- if self.lookup.is_empty() {
- self.lookup
- .resize((self.width as usize) * (self.height as usize), None);
- }
- if sz > 1 {
- let off = (sz - 1) / 2;
- let (x0, y0) = (x - off, y - off);
- for dy in 0..sz {
- for dx in 0..sz {
- self.lookup[(x0 + dx) + (y0 + dy) * (self.width as usize)] = val;
- }
- }
- } else {
- self.lookup[x + y * (self.width as usize)] = val;
- }
- }
-
- /// put a block in (same as [Schematic::set], but less arguments)
+ /// put a block in (same as [Schematic::set], but less arguments and builder-ness). panics!!!
/// ```
/// # use mindus::Schematic;
- /// # use mindus::DynData;
- /// # use mindus::block::Rotation;
///
/// let mut s = Schematic::new(5, 5);
/// s.put(0, 0, &mindus::block::distribution::ROUTER);
/// assert!(s.get(0, 0).unwrap().is_some() == true);
/// ```
- pub fn put(&mut self, x: u16, y: u16, block: &'l Block) -> Result<&Placement<'l>, PlaceError> {
- self.set(x, y, block, DynData::Empty, Rotation::Up)
+ pub fn put(&mut self, x: usize, y: usize, block: &'l Block) -> &mut Self {
+ self.set(x, y, block, DynData::Empty, Rotation::Up).unwrap();
+ self
}
/// set a block
@@ -294,58 +302,17 @@ impl<'l> Schematic<'l> {
/// ```
pub fn set(
&mut self,
- x: u16,
- y: u16,
+ x: usize,
+ y: usize,
block: &'l Block,
data: DynData,
rot: Rotation,
- ) -> Result<&Placement<'l>, PlaceError> {
- let sz = u16::from(block.get_size());
- let off = (sz - 1) / 2;
- if x < off || y < off {
- return Err(PlaceError::Bounds {
- x,
- y,
- sz: block.get_size(),
- w: self.width,
- h: self.height,
- });
- }
- if self.width - x < sz - off || self.height - y < sz - off {
- return Err(PlaceError::Bounds {
- x,
- y,
- sz: block.get_size(),
- w: self.width,
- h: self.height,
- });
- }
- if self.is_region_empty(x - off, y - off, sz, sz) {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement {
- pos: GridPos(x, y),
- block,
- state,
- rot,
- });
- self.fill_lookup(x as usize, y as usize, block.get_size() as usize, Some(idx));
- Ok(&self.blocks[idx])
- } else {
- Err(PlaceError::Overlap { x, y })
- }
- }
-
- pub fn replace(
- &mut self,
- x: u16,
- y: u16,
- block: &'l Block,
- data: DynData,
- rot: Rotation,
- collect: bool,
- ) -> Result<Option<Vec<Placement<'l>>>, PlaceError> {
- let sz = u16::from(block.get_size());
+ ) -> Result<(), PlaceError> {
+ println!(
+ "putting {block:?} at {x} / {y} ({}/{})",
+ self.width, self.height
+ );
+ let sz = usize::from(block.get_size());
let off = (sz - 1) / 2;
if x < off || y < off {
return Err(PlaceError::Bounds {
@@ -365,74 +332,17 @@ impl<'l> Schematic<'l> {
h: self.height,
});
}
- if sz > 1 {
- let mut result = if collect { Some(Vec::new()) } else { None };
- // remove all blocks in the region
- for dy in 0..(sz as usize) {
- for dx in 0..(sz as usize) {
- if let Some(idx) =
- self.lookup[(x as usize + dx) + (y as usize + dy) * (self.width as usize)]
- {
- let prev = self.remove(idx);
- if let Some(ref mut v) = result {
- v.push(prev);
- }
- }
- }
- }
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement {
- pos: GridPos(x, y),
- block,
- state,
- rot,
- });
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(result)
- } else {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos] {
- None => {
- let idx = self.blocks.len();
- let state = block.deserialize_state(data)?;
- self.blocks.push(Placement {
- pos: GridPos(x, y),
- block,
- state,
- rot,
- });
- self.lookup[pos] = Some(idx);
- Ok(if collect { Some(Vec::new()) } else { None })
- }
- Some(idx) => {
- let state = block.deserialize_state(data)?;
- let prev = std::mem::replace(
- &mut self.blocks[idx],
- Placement {
- pos: GridPos(x, y),
- block,
- state,
- rot,
- },
- );
- self.fill_lookup(
- prev.pos.0 as usize,
- prev.pos.1 as usize,
- prev.block.get_size() as usize,
- None,
- );
- self.fill_lookup(x as usize, y as usize, sz as usize, Some(idx));
- Ok(if collect { Some(vec![prev]) } else { None })
- }
- }
- }
+ let state = block.deserialize_state(data)?;
+ let p = Placement { block, state, rot };
+ self.blocks[x][y] = Some(p);
+ Ok(())
}
/// take out a block
/// ```
/// # use mindus::Schematic;
/// # use mindus::DynData;
+
/// # use mindus::block::Rotation;
///
/// let mut s = Schematic::new(5, 5);
@@ -441,7 +351,7 @@ impl<'l> Schematic<'l> {
/// assert!(s.take(0, 0).unwrap().is_some() == true);
/// assert!(s.get(0, 0).unwrap().is_none() == true);
/// ```
- pub fn take(&mut self, x: u16, y: u16) -> Result<Option<Placement<'l>>, PosError> {
+ pub fn take(&mut self, x: usize, y: usize) -> Result<Option<Placement<'l>>, PosError> {
if x >= self.width || y >= self.height {
return Err(PosError {
x,
@@ -450,207 +360,36 @@ impl<'l> Schematic<'l> {
h: self.height,
});
}
- if self.blocks.is_empty() {
- Ok(None)
- } else {
- let pos = (x as usize) + (y as usize) * (self.width as usize);
- match self.lookup[pos] {
- None => Ok(None),
- Some(idx) => Ok(Some(self.remove(idx))),
- }
- }
- }
-
- fn rebuild_lookup(&mut self) {
- self.lookup.clear();
- if !self.blocks.is_empty() {
- self.lookup
- .resize((self.width as usize) * (self.height as usize), None);
- for (i, curr) in self.blocks.iter().enumerate() {
- let sz = curr.block.get_size() as usize;
- let x = curr.pos.0 as usize - (sz - 1) / 2;
- let y = curr.pos.1 as usize - (sz - 1) / 2;
- if sz > 1 {
- for dy in 0..sz {
- for dx in 0..sz {
- self.lookup[(x + dx) + (y + dy) * (self.width as usize)] = Some(i);
- }
- }
- } else {
- self.lookup[x + y * (self.width as usize)] = Some(i);
- }
- }
- }
- }
-
- /// flip it
- pub fn mirror(&mut self, horizontally: bool, vertically: bool) {
- if !self.blocks.is_empty() && (horizontally || vertically) {
- for curr in &mut self.blocks {
- // because position is the bottom left of the center (which changes during mirroring)
- let shift = (u16::from(curr.block.get_size()) - 1) % 2;
- if horizontally {
- curr.pos.0 = self.width - 1 - curr.pos.0 - shift;
- }
- if vertically {
- curr.pos.1 = self.height - 1 - curr.pos.1 - shift;
- }
- if !curr.block.is_symmetric() {
- curr.rot.mirror(horizontally, vertically);
- }
- if let Some(ref mut state) = curr.state {
- curr.block.mirror_state(state, horizontally, vertically);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- /// turn
- /// ```
- /// # use mindus::Schematic;
- /// # use mindus::DynData;
- /// # use mindus::block::Rotation;
- ///
- /// let mut s = Schematic::new(5, 5);
- /// // 0, 0 == bottom left
- /// s.put(0, 0, &mindus::block::turrets::HAIL);
- /// s.rotate(true);
- /// assert!(s.get(0, 4).unwrap().is_some() == true);
- /// ```
- pub fn rotate(&mut self, clockwise: bool) {
- let w = self.width;
- let h = self.height;
- self.width = h;
- self.height = w;
- if !self.blocks.is_empty() {
- for curr in &mut self.blocks {
- let x = curr.pos.0;
- let y = curr.pos.1;
- // because position is the bottom left of the center (which changes during rotation)
- let shift = (u16::from(curr.block.get_size()) - 1) % 2;
- if clockwise {
- curr.pos.0 = y;
- curr.pos.1 = w - 1 - x - shift;
- } else {
- curr.pos.0 = h - 1 - y - shift;
- curr.pos.1 = x;
- }
- if !curr.block.is_symmetric() {
- curr.rot.rotate(clockwise);
- }
- if let Some(ref mut state) = curr.state {
- curr.block.rotate_state(state, clockwise);
- }
- }
- self.rebuild_lookup();
- }
- }
-
- /// resize this schematic
- pub fn resize(&mut self, dx: i16, dy: i16, w: u16, h: u16) -> Result<(), ResizeError> {
- if w > MAX_DIMENSION {
- return Err(ResizeError::TargetWidth(w));
- }
- if h > MAX_DIMENSION {
- return Err(ResizeError::TargetHeight(h));
- }
- if dx <= -(w as i16) || dx >= self.width as i16 {
- return Err(ResizeError::XOffset {
- dx,
- old_w: self.width,
- new_w: w,
- });
- }
- if dy <= -(h as i16) || dy >= self.height as i16 {
- return Err(ResizeError::YOffset {
- dy,
- old_h: self.height,
- new_h: h,
- });
- }
- // check that all blocks fit into the new bounds
- let mut right = 0u16;
- let mut top = 0u16;
- let mut left = 0u16;
- let mut bottom = 0u16;
- let right_bound = dx + w as i16 - 1;
- let top_bound = dy + h as i16 - 1;
- let left_bound = dx;
- let bottom_bound = dy;
- for Placement { pos, block, .. } in &self.blocks {
- let sz = u16::from(block.get_size());
- let (x0, y0, x1, y1) = (
- pos.0 - (sz - 1) / 2,
- pos.1 - (sz - 1) / 2,
- pos.0 + sz / 2,
- pos.1 + sz / 2,
- );
- if (x1 as i16) > right_bound && x1 - right_bound as u16 > right {
- right = x1 - right_bound as u16;
- }
- if (y1 as i16) > top_bound && y1 - top_bound as u16 > top {
- top = y1 - top_bound as u16;
- }
- if (x0 as i16) < left_bound && left_bound as u16 - x0 > left {
- left = left_bound as u16 - x0;
- }
- if (y0 as i16) < bottom_bound && bottom_bound as u16 - y0 > bottom {
- bottom = bottom_bound as u16 - y0;
- }
- }
- if left > 0 || top > 0 || right > 0 || bottom > 0 {
- return Err(TruncatedError {
- right,
- top,
- left,
- bottom,
- })?;
- }
- self.width = w;
- self.height = h;
- for Placement { pos, .. } in &mut self.blocks {
- pos.0 = (pos.0 as i16 + dx) as u16;
- pos.1 = (pos.1 as i16 + dy) as u16;
- }
- Ok(())
- }
-
- /// like rotate(), but 180
- pub fn rotate_180(&mut self) {
- self.mirror(true, true);
- }
-
- #[must_use]
- pub fn pos_iter(&self) -> PosIter {
- PosIter {
- x: 0,
- y: 0,
- w: self.width,
- h: self.height,
- }
+ let b = self.blocks[x][y].take();
+ Ok(b)
}
/// iterate over all the blocks
- pub fn block_iter<'s>(&'s self) -> Iter<'s, Placement<'l>> {
- self.blocks.iter()
+ pub fn block_iter(&self) -> impl Iterator<Item = (GridPos, &Placement<'_>)> {
+ self.blocks.iter().enumerate().filter_map(|(i, p)| {
+ let Some(p) = p else {
+ return None;
+ };
+ Some((GridPos(i / self.height, i % self.height), p))
+ })
}
#[must_use]
/// see how much this schematic costs.
+ /// returns (cost, is_sandbox)
/// ```
/// # use mindus::Schematic;
/// # use mindus::DynData;
/// # use mindus::block::Rotation;
///
/// let mut s = Schematic::new(5, 5);
- /// s.put(0, 0, &mindus::block::turrets::CYCLONE);
- /// // assert_eq!(s.compute_total_cost().0.get_total(), 405);
+ /// s.put(1, 1, &mindus::block::turrets::CYCLONE);
+ /// assert_eq!(s.compute_total_cost().0.get_total(), 405);
/// ```
pub fn compute_total_cost(&self) -> (ItemStorage, bool) {
let mut cost = ItemStorage::new();
let mut sandbox = false;
- for &Placement { block, .. } in &self.blocks {
+ for &Placement { block, .. } in self.blocks.iter().filter_map(|b| b.as_ref()) {
if let Some(curr) = block.get_build_cost() {
cost.add_all(&curr, u32::MAX);
} else {
@@ -665,32 +404,32 @@ impl<'l> Schematic<'l> {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
pub enum NewError {
#[error("invalid schematic width ({0})")]
- Width(u16),
+ Width(usize),
#[error("invalid schematic height ({0})")]
- Height(u16),
+ Height(usize),
}
/// error created by doing stuff out of bounds
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
#[error("position {x} / {y} out of bounds {w} / {h}")]
pub struct PosError {
- pub x: u16,
- pub y: u16,
- pub w: u16,
- pub h: u16,
+ pub x: usize,
+ pub y: usize,
+ pub w: usize,
+ pub h: usize,
}
#[derive(Debug, Error)]
pub enum PlaceError {
#[error("invalid block placement {x} / {y} (size {sz}) within {w} / {h}")]
Bounds {
- x: u16,
- y: u16,
+ x: usize,
+ y: usize,
sz: u8,
- w: u16,
- h: u16,
+ w: usize,
+ h: usize,
},
#[error("overlapping an existing block at {x} / {y}")]
- Overlap { x: u16, y: u16 },
+ Overlap { x: usize, y: usize },
#[error("block state deserialization failed")]
Deserialize(#[from] block::DeserializeError),
}
@@ -746,180 +485,6 @@ impl fmt::Display for TruncatedError {
}
}
-impl fmt::Debug for Schematic<'_> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Display::fmt(self, f)
- }
-}
-
-impl<'l> fmt::Display for Schematic<'l> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- /*
- Because characters are about twice as tall as they are wide, two are used to represent a single block.
- Each block has a single letter to describe what it is + an optional rotation.
- For size-1 blocks, that's "*]" for symmetric and "*>", "*^", "*<", "*v" for rotations.
- Larger blocks are formed using pipes, slashes and minuses to form a border, which is filled with spaces.
- Then, the letter is placed inside followed by the rotation (if any).
- */
-
- // find unique letters for each block, more common blocks pick first
- let mut name_cnt = HashMap::<&str, u16>::new();
- for p in &self.blocks {
- match name_cnt.entry(p.block.get_name()) {
- Entry::Occupied(mut e) => *e.get_mut() += 1,
- Entry::Vacant(e) => {
- e.insert(1);
- }
- }
- }
- // only needed the map for counting
- let mut name_cnt = Vec::from_iter(name_cnt);
- name_cnt.sort_by(|l, r| r.1.cmp(&l.1));
- // set for control characters, space, b'*', DEL and b">^<v]/|\\-"
- let mut used = [
- 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0x01u8, 0xA4u8, 0x00u8, 0x50u8, 0x00u8, 0x00u8, 0x00u8,
- 0x70u8, 0x00u8, 0x00u8, 0x40u8, 0x90u8,
- ];
- let mut types = HashMap::<&str, char>::new();
- for &(name, _) in &name_cnt {
- let mut found = false;
- for c in name.chars() {
- if c > ' ' && c <= '~' {
- let upper = c.to_ascii_uppercase() as usize;
- let lower = c.to_ascii_lowercase() as usize;
- if used[upper >> 3] & (1 << (upper & 7)) == 0 {
- found = true;
- used[upper >> 3] |= 1 << (upper & 7);
- types.insert(name, unsafe { char::from_u32_unchecked(upper as u32) });
- break;
- }
- if lower != upper && used[lower >> 3] & (1 << (lower & 7)) == 0 {
- found = true;
- used[lower >> 3] |= 1 << (lower & 7);
- types.insert(name, unsafe { char::from_u32_unchecked(lower as u32) });
- break;
- }
- }
- }
- if !found {
- // just take whatever symbol's still free (avoids collisions with letters)
- match used.iter().enumerate().find(|(_, &v)| v != u8::MAX) {
- // there's no more free symbols... how? use b'*' instead for all of them (reserved)
- None => {
- types.insert(name, '*');
- }
- Some((i, v)) => {
- let idx = i + v.trailing_ones() as usize;
- used[idx >> 3] |= 1 << (idx & 7);
- types.insert(name, unsafe { char::from_u32_unchecked(idx as u32) });
- }
- }
- }
- }
-
- // coordinates start in the bottom left, so y starts at self.height - 1
- if self.blocks.is_empty() {
- write!(f, "<empty {} * {}>", self.width, self.height)?;
- } else {
- for y in (0..self.height as usize).rev() {
- let mut x = 0usize;
- while x < self.width as usize {
- if let Some(idx) = self.lookup[x + y * (self.width as usize)] {
- let Placement {
- pos,
- block,
- state: _,
- rot,
- } = self.blocks[idx];
- let c = *types.get(block.get_name()).unwrap();
- match block.get_size() as usize {
- 0 => unreachable!(),
- 1 => {
- f.write_char(c)?;
- match rot {
- _ if block.is_symmetric() => f.write_char(']')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- }
- s => {
- let y0 = pos.1 as usize - (s - 1) / 2;
- if y == y0 + (s - 1) {
- // top row, which looks like /---[...]---\
- f.write_char('/')?;
- if s == 2 {
- // label & rotation are in this row
- f.write_char(c)?;
- match rot {
- _ if block.is_symmetric() => f.write_char('-')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- } else {
- // label & rotation are not in this row
- for _ in 0..(2 * s - 2) {
- f.write_char('-')?;
- }
- }
- f.write_char('\\')?;
- } else if y == y0 {
- // bottom row, which looks like \---[...]---/
- f.write_char('\\')?;
- for _ in 0..(2 * s - 2) {
- f.write_char('-')?;
- }
- f.write_char('/')?;
- } else if s > 2 && y == y0 + s / 2 {
- // middle row with label
- f.write_char('|')?;
- for cx in 0..(2 * s - 2) {
- if cx == s - 2 {
- f.write_char(c)?;
- } else if cx == s - 1 {
- match rot {
- _ if block.is_symmetric() => f.write_char(' ')?,
- Rotation::Right => f.write_char('>')?,
- Rotation::Up => f.write_char('^')?,
- Rotation::Left => f.write_char('<')?,
- Rotation::Down => f.write_char('v')?,
- }
- } else {
- f.write_char(' ')?;
- }
- }
- f.write_char('|')?;
- } else {
- // middle row, which looks like | [...] |
- f.write_char('|')?;
- for _ in 0..(2 * s - 2) {
- f.write_char(' ')?;
- }
- f.write_char('|')?;
- }
- }
- }
- x += block.get_size() as usize;
- } else {
- f.write_str(" ")?;
- x += 1;
- }
- }
- writeln!(f)?;
- }
- // print the letters assigned to blocks
- for (k, _) in name_cnt {
- let v = *types.get(k).unwrap();
- write!(f, "\n({v}) {k}")?;
- }
- }
- Ok(())
- }
-}
-
const SCHEMATIC_HEADER: u32 =
((b'm' as u32) << 24) | ((b's' as u32) << 16) | ((b'c' as u32) << 8) | (b'h' as u32);
@@ -940,12 +505,12 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
}
let buff = buff.deflate()?;
let mut buff = DataRead::new(&buff);
- let w = buff.read_i16()?;
- let h = buff.read_i16()?;
- if w < 0 || h < 0 || w as u16 > MAX_DIMENSION || h as u16 > MAX_DIMENSION {
+ let w = buff.read_i16()? as usize;
+ let h = buff.read_i16()? as usize;
+ if w > MAX_DIMENSION || h > MAX_DIMENSION {
return Err(ReadError::Dimensions(w, h));
}
- let mut schematic = Schematic::new(w as u16, h as u16);
+ let mut schematic = Schematic::new(w, h);
buff.read_map(&mut schematic.tags)?;
let num_table = buff.read_i8()?;
if num_table < 0 {
@@ -953,23 +518,23 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
}
let mut block_table = Vec::new();
block_table.reserve(num_table as usize);
- for _ in 0..num_table {
+ for _ in 0..dbg!(num_table) {
let name = buff.read_utf()?;
match self.0.get(name) {
None => return Err(ReadError::NoSuchBlock(name.to_owned())),
Some(b) => block_table.push(b),
}
}
- let num_blocks = buff.read_i32()?;
+ let num_blocks = dbg!(buff.read_i32()?);
if num_blocks < 0 || num_blocks as u32 > MAX_BLOCKS {
return Err(ReadError::BlockCount(num_blocks));
}
for _ in 0..num_blocks {
- let idx = buff.read_i8()?;
+ let idx = dbg!(buff.read_i8()?);
if idx < 0 || idx as usize >= block_table.len() {
return Err(ReadError::BlockIndex(idx, block_table.len()));
}
- let pos = GridPos::from(buff.read_u32()?);
+ let pos = dbg!(GridPos::from(buff.read_u32()?));
let block = block_table[idx as usize];
let config = if version < 1 {
block.data_from_i32(buff.read_i32()?, pos)?
@@ -1006,7 +571,9 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
// use string keys here to avoid issues with different block refs with the same name
let mut block_map = HashMap::new();
let mut block_table = Vec::new();
- for curr in &data.blocks {
+ let mut block_count = 0i32;
+ for curr in data.blocks.iter().filter_map(|b| b.as_ref()) {
+ block_count += 1;
if let Entry::Vacant(e) = block_map.entry(curr.block.get_name()) {
e.insert(block_table.len() as u32);
block_table.push(curr.block.get_name());
@@ -1021,20 +588,17 @@ impl<'l> Serializer<Schematic<'l>> for SchematicSerializer<'l> {
rbuff.write_utf(name)?;
}
// don't have to check data.blocks.len() because dimensions don't allow exceeding MAX_BLOCKS
- rbuff.write_i32(data.blocks.len() as i32)?;
- let mut num = 0;
- for curr in &data.blocks {
+ rbuff.write_i32(block_count)?;
+ for (pos, curr) in data.block_iter() {
rbuff.write_i8(block_map[curr.block.get_name()] as i8)?;
- rbuff.write_u32(u32::from(curr.pos))?;
+ rbuff.write_u32(pos.into())?;
let data = match curr.state {
None => DynData::Empty,
Some(ref s) => curr.block.serialize_state(s)?,
};
DynSerializer.serialize(&mut rbuff, &data)?;
rbuff.write_u8(curr.rot.into())?;
- num += 1;
}
- assert_eq!(num, data.blocks.len());
rbuff.inflate(buff)?;
Ok(())
}
@@ -1048,8 +612,8 @@ pub enum ReadError {
Header(u32),
#[error("unsupported version ({0})")]
Version(u8),
- #[error("invalid schematic dimensions ({0} * {1})")]
- Dimensions(i16, i16),
+ #[error("invalid schematic dimensions ({0} / {1})")]
+ Dimensions(usize, usize),
#[error("invalid block table size ({0})")]
TableSize(i8),
#[error("unknown block {0:?}")]
@@ -1064,6 +628,8 @@ pub enum ReadError {
ReadState(#[from] dynamic::ReadError),
#[error("deserialized block could not be placed")]
Placement(#[from] PlaceError),
+ #[error(transparent)]
+ Decompress(#[from] super::DecompressError),
}
#[derive(Debug, Error)]
@@ -1078,6 +644,8 @@ pub enum WriteError {
StateSerialize(#[from] block::SerializeError),
#[error("failed to write block data")]
WriteState(#[from] dynamic::WriteError),
+ #[error(transparent)]
+ Compress(#[from] super::CompressError),
}
impl<'l> SchematicSerializer<'l> {
@@ -1088,7 +656,7 @@ impl<'l> SchematicSerializer<'l> {
/// let reg = build_registry();
/// let mut ss = SchematicSerializer(&reg);
/// let s = ss.deserialize_base64(string).unwrap();
- /// assert!(s.get(0, 0).unwrap().unwrap().block.name() == "payload-router");
+ /// assert!(s.get(1, 1).unwrap().unwrap().block.name() == "payload-router");
/// ```
pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> {
let mut buff = Vec::<u8>::new();
@@ -1130,88 +698,46 @@ pub enum W64Error {
Content(#[from] WriteError),
}
-pub struct PosIter {
- x: u16,
- y: u16,
- w: u16,
- h: u16,
-}
-
-impl Iterator for PosIter {
- type Item = GridPos;
-
- fn next(&mut self) -> Option<Self::Item> {
- if self.w > 0 && self.y < self.h {
- let p = GridPos(self.x, self.y);
- self.x += 1;
- if self.x == self.w {
- self.x = 0;
- self.y += 1;
- }
- Some(p)
- } else {
- None
- }
- }
-
- fn size_hint(&self) -> (usize, Option<usize>) {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- (end - pos, Some(end - pos))
- }
-
- fn count(self) -> usize {
- let pos = (self.x as usize) + (self.y as usize) * (self.w as usize);
- let end = (self.w as usize) * (self.h as usize);
- end - pos
- }
-
- fn last(self) -> Option<Self::Item> {
- // self.y < self.h implies self.h > 0
- if self.w > 0 && self.y < self.h {
- Some(GridPos(self.w - 1, self.h - 1))
- } else {
- None
- }
- }
-}
-
-impl FusedIterator for PosIter {}
-
#[cfg(test)]
mod test {
use super::*;
-
- macro_rules! test_iter {
- ($name:ident, $it:expr, $($val:expr),+) => {
- #[test]
- fn $name() {
- let mut it = $it;
- $(test_iter!(impl it, $val);)+
- }
- };
- (impl $it:ident, $val:literal) => {
- for _ in 0..$val {
- assert_ne!($it.next(), None, "iterator returned None too early");
- }
- };
- (impl $it:ident, $val:expr) => {
- assert_eq!($it.next(), $val);
- };
- }
+ fn unwrap_pretty<T, E: std::fmt::Display + std::error::Error>(r: Result<T, E>) -> T {
+ match r {
+ Ok(t) => t,
+ Err(e) => {
+ use std::error::Error;
+ eprintln!("{e}");
+ let mut err_ref = &e as &dyn Error;
+ loop {
+ let Some(next) = err_ref.source() else {
+ panic!();
+ };
+ eprintln!("\tFrom: {next}");
+ err_ref = next;
+ }
+ }
+ }
+ }
macro_rules! test_schem {
- ($name:ident, $($val:expr),+) => {
+ ($name:ident, $($val:expr);+;) => {
#[test]
fn $name() {
let reg = crate::block::build_registry();
let mut ser = SchematicSerializer(&reg);
$(
- let parsed = ser.deserialize_base64($val).unwrap();
- println!("{}", parsed.tags.get("name").unwrap());
- let unparsed = ser.serialize_base64(&parsed).unwrap();
- let parsed2 = ser.deserialize_base64(&unparsed).unwrap();
- assert_eq!(parsed, parsed2);
+ let parsed = unwrap_pretty(ser.deserialize_base64($val));
+ println!("\x1b[38;5;2mdeserialized\x1b[0m {}", parsed.tags.get("name").unwrap());
+ let unparsed = unwrap_pretty(ser.serialize_base64(&parsed));
+ println!("\x1b[38;5;2mserialized\x1b[0m {}", parsed.tags.get("name").unwrap());
+ let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed));
+ println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap());
+ if parsed != parsed2 {
+ // TODO serialization currently lossy? missing right side mostly
+ parsed2.render().save("p2.png").unwrap();
+ parsed.render().save("p1.png").unwrap();
+ panic!("DIFFERENT! see `p1.png` != `p2.png`")
+ }
)*
}
};
@@ -1219,22 +745,54 @@ mod test {
test_schem! {
ser_de,
- "bXNjaAF4nCVNy07DMBCcvC1c4MBnoNz4G8TBSSxRycSRbVr646iHlmUc2/KOZ3dmFo9QDdrVfFkMb9Gsi5mgFxvncNzS0a8Aemcm6yLq948Bz2eTbBjtTwpmTj7gafs00Y6zX0/2Qt6dzLdLeNj8mbrVLxZ6ciamcQlH59BHH5iAYTKJeOGCF6AisFSoBxF55V+hJm1Lvwca8lpVIuzlS0eGLoMqTGUG6OLRJes3Mw40E5ijc2QedkPuU3DfLX0eHriDsgMapaScu9zkT26o5Uq8EmV/zS5vi4tr/wHvJE7M",
- "bXNjaAF4nE2MzWrEMAyEJ7bjdOnPobDQvfUF8kSlhyTWFlOv3VWcQvv0lRwoawzSjL4ZHOAtXJ4uhEdi+oz8ek5bDCvuA60Lx68aSwbg0zRTWmHe3j2emWI+F14ojEvJYYsVD5RoqVzSzy8xDjNNlzGXQHi5gVO8SvnIZasCnW4uM8fwQf9tT9+Ua1OUV0GBI9ozHToY6IeDtaIACxkOnaoe1rVrg2RV1cP0CuycLA5+LxuUU+U055W0Yrb4sEcGNQ3u1NTh9iHmH6qaOTI=",
- "bXNjaAF4nE2R226kQAxEzW1oYC5kopV23/IDfMw+R3ng0klaYehsD6w2+fqtamuiDILCLtvH9MgPaTLJl/5ipR3cy4MN9s2FB//PTVaayV7H4N5X5xcR2c39YOerpI9Pe/kVrFuefRjt1A3BTS+2G/0ybW6V41+7rDGyy9UGjNnGtQt+W78C7ZCcgVSD7S/d4kH8+W3q7P5sbrr1nb85N9DeznZcg58/PlFxx6V77tqNr/1lQOr0anuQ7eQCCn2QQ6Rvy+z7Cb7Ib9xSSJpICsGDV5bxoVJKxpSRLIdUKrVkBQoSkVxYJDuWq5SaNByboUEYJ5LgmFlZRhdejit6oDO5Uw/trDTqgWfgpCqFiiG91MVL7IJfLKck3NooyBDEZM4Gw+9jtJOEXgQZ7TQAJZSaM+POFd5TSWpIoVHEVsqrlUcX8xq+U2pi94wyCHZpICn625jAGdVy4DxGpdom2gXeKu2OIw+6w5E7UABnMgKO9CgxOukiHBGjmGz1dFp+VQO57cA7pUR4+wVvFd5q9x2aQT0r/Ew4k/FfPyvunjhGaPgPoVJdLw==",
- "bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+",
- "bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe"
+ "bXNjaAF4nCVNy07DMBCcvC1c4MBnoNz4G8TBSSxRycSRbVr646iHlmUc2/KOZ3dmFo9QDdrVfFkMb9Gsi5mgFxvncNzS0a8Aemcm6yLq948Bz2eTbBjtTwpmTj7gafs00Y6zX0/2Qt6dzLdLeNj8mbrVLxZ6ciamcQlH59BHH5iAYTKJeOGCF6AisFSoBxF55V+hJm1Lvwca8lpVIuzlS0eGLoMqTGUG6OLRJes3Mw40E5ijc2QedkPuU3DfLX0eHriDsgMapaScu9zkT26o5Uq8EmV/zS5vi4tr/wHvJE7M";
+ "bXNjaAF4nE2MzWrEMAyEJ7bjdOnPobDQvfUF8kSlhyTWFlOv3VWcQvv0lRwoawzSjL4ZHOAtXJ4uhEdi+oz8ek5bDCvuA60Lx68aSwbg0zRTWmHe3j2emWI+F14ojEvJYYsVD5RoqVzSzy8xDjNNlzGXQHi5gVO8SvnIZasCnW4uM8fwQf9tT9+Ua1OUV0GBI9ozHToY6IeDtaIACxkOnaoe1rVrg2RV1cP0CuycLA5+LxuUU+U055W0Yrb4sEcGNQ3u1NTh9iHmH6qaOTI=";
+ "bXNjaAF4nE2R226kQAxEzW1oYC5kopV23/IDfMw+R3ng0klaYehsD6w2+fqtamuiDILCLtvH9MgPaTLJl/5ipR3cy4MN9s2FB//PTVaayV7H4N5X5xcR2c39YOerpI9Pe/kVrFuefRjt1A3BTS+2G/0ybW6V41+7rDGyy9UGjNnGtQt+W78C7ZCcgVSD7S/d4kH8+W3q7P5sbrr1nb85N9DeznZcg58/PlFxx6V77tqNr/1lQOr0anuQ7eQCCn2QQ6Rvy+z7Cb7Ib9xSSJpICsGDV5bxoVJKxpSRLIdUKrVkBQoSkVxYJDuWq5SaNByboUEYJ5LgmFlZRhdejit6oDO5Uw/trDTqgWfgpCqFiiG91MVL7IJfLKck3NooyBDEZM4Gw+9jtJOEXgQZ7TQAJZSaM+POFd5TSWpIoVHEVsqrlUcX8xq+U2pi94wyCHZpICn625jAGdVy4DxGpdom2gXeKu2OIw+6w5E7UABnMgKO9CgxOukiHBGjmGz1dFp+VQO57cA7pUR4+wVvFd5q9x2aQT0r/Ew4k/FfPyvunjhGaPgPoVJdLw==";
+ "bXNjaAF4nD1TDUxTVxS+r6+vr30triCSVjLXiulKoAjMrJRkU8b4qSgLUAlIZ1rah7yt9L31h1LMMCNbRAQhYrKwOnEslhCcdmzJuohL1DjZT4FJtiVsoG5LFtzmGgGngHm790mam7x77ne+c945934HKIAcB2K3vYUGScXmWvM+TQ3jYhysG8idtNfhYTgfAw8ASFz2RtrlBaKG12VA6X1KMjg8fgfT6KLBJi7osfsYH21oYdpoD6A4NkB7DG7WSQOJl/X4IPYM426loeU0bABSv9vF2p3I1cI4PKyB87AO2gu9gGi1+10+kMTCiCYXGzActvtoWEY+ABhcIgzaOBCJ4EZICYDx6xAV86vCdx2IAS5QJJAEIRkQ4XAjAHSIIITBUCCGRwIuESCgheEIkwgYIpEAF4I3wSw9bWccTpvNmVkZy5raWT1p3r+vajJ2odyQb+HAW9HxvV556vfvpNy4oVUfDyq36Kyqe73xsdemprMyv52uAreYwcXzJaPU+aDp8fFM24nuzUvVqYo9yr7CjFT/aDDzUUz8S8W7g+X3VCpVnargblNubl4kI1q6J+cFPH2HS6VSF5xzZWhCyYCKO2FjqAEprB9WRsJbwNFFoLKhITRCQheBbByQCMAQQwow1I8M9oPJ2870npqvvq5RvvfFyYE3hjrLmst3TixrV0XSN08Uax/UrMSeHdmKDdj8uhh3Pef2Wa+qDljrj82pK+aM300sl0eTrC/rL3zzZKZhRWFMq+mLvvTZb0bbweGZL/85ywwnl4RLzR9MBdIGy0LJowOWHxoOY2EiaJ/7s7ZP0Tg2wjWb3y6Lm3IPRNonw/0yT/+lZsdFy/LmUEp2RojHl68B41zDx43WJ/qANkwdVOvGtxjzpgo/keUURn2XK6zerz9Km10w3Vb8Ww/t/UdmHyx7fXwEcPiP0w1Xx9f+/m/X/d13Wiees8yPnk69ePlS9Yuf9sQf1dvVB27mm68U+51Fj7emzS+mzw1jzwuvTKFXHoK30l9EXctVlozIiSPdpk5djrW965BmV1XW4qsp8kNXmtWztdklXXTa0u6lO0d1+GS3TV/Q95O+17+S23Hs5sIfP4e/uqvd9oo+p7u0cYiPb4+9f/L+Qn3PmuXDdDai/ev0ts69I9nuNTOXp9HfOmoy/a5Y9D2cYYsebq+cKgB1V9vXdYFfOz7vWiVCLNnUUVkLOGO9umVN0jl2KoIjYSINEzgUORoDBKAnJwSLTLikQOBSAoC0ABBAbMgDWYIuBBeFRE7CbBCXCAwxFBAJPRgCSAFADBlykokcZCKHFAkPbSRKRaFUUsRGUyZLTJksMWWyjSlDJKhfFALZmFAJdFPo1+gkQVKXw/EW8/zToeZ5fh0t/H+V6k8+";
+ "bXNjaAF4nGNgZ2BjZmDJS8xNZRByrkzOyc9LdctJLEpVT1F4v3AaA3dKanFyUWZBSWZ+HgMDA1tOYlJqTjEDU3QsFwN/eWJJapFuakVJUWJySX4RA3tSYglQpJKBPRliEgNXQX45UElefkoqA39SUWZKeqpucn5eWWolSDmQlVKaWcIgkpyfm1RaDLJDNz01L7UoEWQaf3JRZX5aTmlmim5uZkUqUCA3M7koX7egKD85tbgYqIIlIzEzB+gqQQYwYGYEEkwgxMjAAuQCKSYOZgam//8ZWP7/+/+HgZGZBSTPDlEGVMEKVssAooAMNqAWBpA0SCdQKTMDMzvEZAZGRhCXBZXLyv7///8cIC4AKgZaCOKGAHEh0DBWBpAKIAW0hQNkAR9Q16+KOXtDbhfNNhBQneu5XNV+o/0FSYFCtbrHC+dm3v/MnK3TnKUYGCv0+X3uygksNwzr3jbr755T/O3NuiOxk+7YX7lSoNb3oW3CUq7vKxq4bn1rUKqJsqldfsLX2KkoICQ679L8bW8fCLaY3K+LfGLIe6hoXlaW3iMvrsUq7Hc9Mq1W/OrydlRf+VK9+Ov1iSmsK1deCPKVPF69dG+I5RQ3qSf2PLmlH2bkLwgT4Q3V5+3qnBDPqcG1dNuqZfETim+6G0UqT9b5bGsUznqqb7GZxoxc5eUMT/JvvC79UdlruPvxJis9XhbeTZXLN+68WFB41+DkNRv7uZEGOr/2rvPPu8ZfyXRLI+zoUnmLXRu3+nz0EnP1Omq39TLX3c23cleZ8F62FSnMVCviO2H6tWXB7z2LpA02vleTOXiJK20BT+ADsencfQd0tlqrfQuoWut5dHaX1OP/KwIY5r66Zp4T9+2p241L0XvPfu5w/Zu3bNX77V89kt42zOLf9jJ9vk+msV1vy/Knlywv7Lh4NEY7fvHay0t3Sxo+2q918+je/O23P+50/qEWe45XqGzaR1vh0h1idRwBYZter2DKPJv559VDhbXSHzgin2x8PeXIKsvmtRIVB5V5b/1zcBP+f7VpfuP1OLcJKiePP7n8paroYrul0uF88dp5619+MN8Z7WT0p7DTUqftYOt3XqN51hGf+IVDH0UwcDKwAZMFMwCWiVNe";
+ "bXNjaAF4nGNgZGBkZmDJS8xNZeBMrShIzSvOLEtl4E5JLU4uyiwoyczPY2BgYMtJTErNKWZgio5lZODPzUwuytctKMpPTi0uzi8CyjMygAAfA4PQ+Yo5by9u9GxmZGB9GME502nTzKW+Aht/FJq1ez+o8nzYGn5n+wHR70VVf23t9tutu58/Xbm+qr5t/v+PAa93zIn+L1BpFbXfY17fNf1Jyxd/7X7yMuOv0qjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCo0KjQqNCoEJWFHp987V9uXjv/9y4GAOhu6pc=";
+ }
+
+ #[test]
+ fn block_iter() {
+ macro_rules! test_iter {
+ ($it:ident, $($val:expr;)*) => {
+ $(assert_eq!($it.next().map(|(pos, p)| (pos, p.block)), $val);)*
+ };
+ }
+ macro_rules! pair {
+ ($x:literal,$y:literal,$v:expr) => {
+ Some((GridPos($x, $y), &$v))
+ };
+ () => {
+ None
+ };
+ }
+ use crate::block::all::*;
+ let mut s = Schematic::new(3, 3);
+ s.put(0, 0, &DISTRIBUTOR)
+ .put(0, 1, &JUNCTION)
+ .put(1, 1, &PHASE_CONVEYOR)
+ .put(2, 0, &ROUTER)
+ .put(1, 1, &CONVEYOR);
+ let mut it = s.block_iter();
+ test_iter![
+ it,
+ pair!(0, 0, DISTRIBUTOR);
+ pair!(0, 1, JUNCTION);
+ pair!(1, 1, CONVEYOR);
+ pair!(2, 0, ROUTER);
+ pair!( );
+ ];
+ let reg = crate::block::build_registry();
+ let mut s = SchematicSerializer(&reg);
+
+ let s = s.deserialize_base64("bXNjaAF4nDXKywqAIBQA0fFRBH1itDC7C8E01IT+vgia1VkMFmOwyR3C0N0VG/Mu1ZdwtpATMEa3SazoZdVMPqcudy7/DJovpV4peAAt0xF6").unwrap();
+ let mut it = s.block_iter();
+ test_iter![it,
+ pair!(0, 0, CONVEYOR);
+ pair!(2, 1, VAULT);
+ pair!();
+ ];
}
-
- test_iter!(
- block_iter,
- Schematic::new(3, 4).pos_iter(),
- Some(GridPos(0, 0)),
- Some(GridPos(1, 0)),
- Some(GridPos(2, 0)),
- Some(GridPos(0, 1)),
- 7,
- Some(GridPos(2, 3)),
- None
- );
}
diff --git a/src/exe/draw.rs b/src/exe/draw.rs
index 0a95634..1b7830c 100644
--- a/src/exe/draw.rs
+++ b/src/exe/draw.rs
@@ -1,5 +1,5 @@
use mindus::build_registry;
-use mindus::Renderer;
+use mindus::Renderable;
use mindus::SchematicSerializer;
use std::env::Args;
@@ -18,7 +18,7 @@ pub fn main(args: Args) {
if !first || need_space {
println!();
}
- Renderer::render_schematic(&s).save("x.png").unwrap();
+ s.render().save("x.png").unwrap();
}
// continue processing literals & maybe interactive mode
Err(e) => {
diff --git a/src/exe/map.rs b/src/exe/map.rs
index 13955cd..92653d8 100644
--- a/src/exe/map.rs
+++ b/src/exe/map.rs
@@ -1,5 +1,5 @@
use mindus::data::DataRead;
-use mindus::{build_registry, Renderer};
+use mindus::{build_registry, Renderable};
use mindus::{MapSerializer, Serializer};
use std::env::Args;
@@ -14,7 +14,7 @@ pub fn main(args: Args) {
match ms.deserialize(&mut DataRead::new(&s)) {
Err(e) => print_err!(e, "fail"),
Ok(m) => {
- Renderer::render_map(&m).save("x.png").unwrap();
+ m.render().save("x.png").unwrap();
}
}
}
diff --git a/src/exe/mod.rs b/src/exe/mod.rs
index e56922a..aa49f29 100644
--- a/src/exe/mod.rs
+++ b/src/exe/mod.rs
@@ -1,23 +1,19 @@
mod draw;
mod map;
-mod print;
macro_rules! print_err {
- ($err:expr, $($msg:tt)*) => {
- {
- use std::error::Error;
- let err = $err;
- eprint!($($msg)*);
- eprintln!(": {err}");
- let mut err_ref = &err as &dyn Error;
- loop
- {
- if let Some(next) = err_ref.source()
- {
- eprintln!("\tSource: {next}");
- err_ref = next;
- }
- else {break;}
+ ($err:expr, $($msg:tt)*) => {{
+ use std::error::Error;
+ let err = $err;
+ eprint!($($msg)*);
+ eprintln!(": {err}");
+ let mut err_ref = &err as &dyn Error;
+ loop {
+ let Some(next) = err_ref.source() else {
+ break;
+ };
+ eprintln!("\tSource: {next}");
+ err_ref = next;
}
}
};
@@ -28,10 +24,9 @@ fn main() {
let mut args = std::env::args();
args.next().unwrap(); // path to executable
match args.next() {
- None => eprintln!("Not enough arguments, valid commands are: draw, print"),
- Some(s) if s == "print" => print::main(args),
+ None => eprintln!("Not enough arguments, valid commands are: draw, map"),
Some(s) if s == "draw" => draw::main(args),
Some(s) if s == "map" => map::main(args),
- Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, print"),
+ Some(s) => eprintln!("Unknown argument {s}, valid commands are: draw, map"),
}
}
diff --git a/src/exe/print.rs b/src/exe/print.rs
deleted file mode 100644
index 25af8a4..0000000
--- a/src/exe/print.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use std::env::Args;
-
-use mindus::build_registry;
-use mindus::{Schematic, SchematicSerializer};
-
-use crate::print_err;
-
-pub fn main(args: Args) {
- let reg = build_registry();
- let mut ss = SchematicSerializer(&reg);
- let mut first = true;
- let mut need_space = false;
- // process schematics from command line
- for curr in args {
- match ss.deserialize_base64(&curr) {
- Ok(s) => {
- if !first || need_space {
- println!();
- }
- first = false;
- need_space = true;
- println!("Schematic: {curr}");
- print_schematic(&s);
- }
- // continue processing literals & maybe interactive mode
- Err(e) => {
- if need_space {
- println!();
- }
- first = false;
- need_space = false;
- print_err!(e, "Could not read schematic");
- }
- }
- }
-}
-
-pub fn print_schematic(s: &Schematic) {
- if let Some(name) = s.tags.get("name") {
- if !name.is_empty() {
- println!("Name: {name}");
- }
- }
- if let Some(desc) = s.tags.get("description") {
- if !desc.is_empty() {
- println!("Desc: {desc}");
- }
- }
- if let Some(labels) = s.tags.get("labels") {
- if !labels.is_empty() && labels != "[]" {
- println!("Tags: {:?}", labels);
- }
- }
- let (cost, sandbox) = s.compute_total_cost();
- if !cost.is_empty() {
- println!(
- "Build cost: {cost}{}",
- if sandbox { " (Sandbox only)" } else { "" }
- );
- } else if sandbox {
- println!("Can only be built in the Sandbox");
- }
- println!("\n{s}");
-}
diff --git a/src/item/storage.rs b/src/item/storage.rs
index 45397f5..6eeb018 100644
--- a/src/item/storage.rs
+++ b/src/item/storage.rs
@@ -1,4 +1,3 @@
-use std::error::Error;
use std::fmt;
use std::iter::{Enumerate, FusedIterator};
use std::marker::PhantomData;
@@ -43,6 +42,12 @@ where
}
#[must_use]
+ /// total items
+ pub fn get_total(&self) -> u64 {
+ self.total
+ }
+
+ #[must_use]
/// check if its empty
///
/// ```
@@ -379,44 +384,22 @@ where
}
}
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("adding {add} to current {have} would exceed {max}")]
pub struct TryAddError {
pub have: u32,
pub add: u32,
pub max: u32,
}
-impl fmt::Display for TryAddError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(
- f,
- "adding {:?} to current {} would exceed {}",
- self.add, self.have, self.max
- )
- }
-}
-
-impl Error for TryAddError {}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("removing {sub} from current {have} would drop below {min}")]
pub struct TrySubError {
pub have: u32,
pub sub: u32,
pub min: u32,
}
-impl fmt::Display for TrySubError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(
- f,
- "removing {} from current {} would drop below {}",
- self.sub, self.have, self.min
- )
- }
-}
-
-impl Error for TrySubError {}
-
#[derive(Clone, Debug)]
pub struct Iter<'l> {
base: Enumerate<slice::Iter<'l, u32>>,
diff --git a/src/lib.rs b/src/lib.rs
index 1fe9fe7..776e79d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,6 @@ mod utils;
pub use block::build_registry;
pub use data::dynamic::DynData;
pub use data::map::{Map, MapSerializer};
-pub use data::renderer::Renderer;
+pub use data::renderer::Renderable;
pub use data::schematic::{Schematic, SchematicSerializer};
pub use data::Serializer;
diff --git a/src/team.rs b/src/team.rs
index 596798f..da4a34f 100644
--- a/src/team.rs
+++ b/src/team.rs
@@ -1,4 +1,3 @@
-use std::error::Error;
use std::fmt;
use image::Rgb;
@@ -38,7 +37,8 @@ impl TryFrom<u16> for Team {
}
}
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
+#[error("no content of type Team for value {0}")]
pub struct TryFromU16Error(pub u16);
impl From<Team> for u8 {
@@ -62,19 +62,12 @@ impl fmt::Display for Team {
3 => f.write_str("Malis"),
4 => f.write_str("Green"),
5 => f.write_str("Blue"),
+ 6 => f.write_str("Neoplastic"),
id => write!(f, "Team #{id}"),
}
}
}
-impl fmt::Display for TryFromU16Error {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "no content of type Team for value {}", self.0)
- }
-}
-
-impl Error for TryFromU16Error {}
-
const TEAM_NAMES: &str = include_str!("../res/team_names.txt");
impl Content for Team {
@@ -94,6 +87,7 @@ impl Content for Team {
3 => "malis",
4 => "green",
5 => "blue",
+ 6 => "neoplastic",
// dark magic: offsets manually computed, then rely on the format "...|team#{i}|..."
i @ 6..=9 => {
// length: 7 ("team#" (5) + 1 digit + "|" (1))
diff --git a/src/utils/array.rs b/src/utils/array.rs
new file mode 100644
index 0000000..6685baa
--- /dev/null
+++ b/src/utils/array.rs
@@ -0,0 +1,63 @@
+use std::{
+ fmt::{Debug, Write},
+ ops::Deref,
+};
+#[derive(Clone, PartialEq, Eq)]
+pub struct Array2D<T: Clone> {
+ width: usize,
+ height: usize,
+ /// column
+ data: Box<[T]>,
+}
+
+impl<T: Debug + Clone> Debug for Array2D<Option<T>> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("Arr[\n")?;
+ for y in (0..self.height).rev() {
+ for x in 0..self.width {
+ let t = &self[x][y];
+ if let Some(t) = t {
+ t.fmt(f)?;
+ } else {
+ f.write_char('_')?;
+ }
+ f.write_str(", ")?;
+ }
+ f.write_char('\n')?;
+ }
+ f.write_char(']')?;
+ Ok(())
+ }
+}
+
+impl<T: Clone> Array2D<T> {
+ pub fn new(fill: T, width: usize, height: usize) -> Array2D<T> {
+ Array2D {
+ width,
+ height,
+ data: vec![fill; width * height].into_boxed_slice(),
+ }
+ }
+}
+
+impl<T: Clone> Deref for Array2D<T> {
+ type Target = Box<[T]>;
+ /// a sin it is
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: Clone> std::ops::Index<usize> for Array2D<T> {
+ type Output = [T];
+
+ fn index(&self, x: usize) -> &Self::Output {
+ &self.data[self.height * x..self.height * (x + 1)]
+ }
+}
+
+impl<T: Clone> std::ops::IndexMut<usize> for Array2D<T> {
+ fn index_mut(&mut self, x: usize) -> &mut Self::Output {
+ &mut self.data[self.height * x..self.height * (x + 1)]
+ }
+}
diff --git a/src/utils/image.rs b/src/utils/image.rs
index de6cf6b..959b633 100644
--- a/src/utils/image.rs
+++ b/src/utils/image.rs
@@ -1,17 +1,39 @@
use fast_image_resize as fr;
-use image::{Rgb, Rgba, RgbaImage};
+use image::{GenericImageView, Rgb, Rgba, RgbaImage};
use std::num::NonZeroU32;
+use blurslice::gaussian_blur_bytes;
+
pub trait ImageUtils {
+ /// Tint this image with the color
fn tint(&mut self, color: Rgb<u8>) -> &mut Self;
-
- fn repeat(&mut self, with: &RgbaImage) -> &mut Self;
-
- fn overlay(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self;
-
+ /// Repeat with over self
+ fn repeat(&mut self, with: &Self) -> &mut Self;
+ /// Overlay with onto self (does not blend)
+ fn overlay(&mut self, with: &Self, x: u32, y: u32) -> &mut Self;
+ /// rotate
+ fn rotate(&mut self, times: u8) -> &mut Self;
+ /// shadow
+ fn shadow(&mut self) -> &mut Self;
+ /// silhouette
+ fn silhouette(&mut self) -> &mut Self;
+ /// scale a image
+ ///
+ /// SAFETY: to and width and height cannot be 0.
unsafe fn scale(self, to: u32) -> Self;
}
impl ImageUtils for RgbaImage {
+ fn rotate(&mut self, times: u8) -> &mut Self {
+ use image::imageops::{rotate180, rotate270, rotate90};
+ match times {
+ 2 => *self = rotate180(self),
+ 1 => *self = rotate90(self),
+ 3 => *self = rotate270(self),
+ _ => {}
+ }
+ self
+ }
+
fn tint(&mut self, color: Rgb<u8>) -> &mut Self {
let [tr, tg, tb] = [
color[0] as f32 / 255.0,
@@ -39,7 +61,7 @@ impl ImageUtils for RgbaImage {
for j in 0..with.height() {
for i in 0..with.width() {
let get = with.get_pixel(i, j);
- if get[3] > 5 {
+ if get[3] > 128 {
self.put_pixel(i + x, j + y, *get);
}
}
@@ -47,9 +69,6 @@ impl ImageUtils for RgbaImage {
self
}
- /// scales a image
- ///
- /// SAFETY: to and width and height cannot be 0.
unsafe fn scale(self, to: u32) -> Self {
debug_assert_ne!(to, 0);
debug_assert_ne!(self.width(), 0);
@@ -68,4 +87,36 @@ impl ImageUtils for RgbaImage {
.unwrap();
RgbaImage::from_raw(to.get(), to.get(), dst.into_vec()).unwrap()
}
+
+ fn silhouette(&mut self) -> &mut Self {
+ for pixel in self.pixels_mut() {
+ if pixel[3] < 128 {
+ pixel[2] /= 10;
+ pixel[1] /= 10;
+ pixel[0] /= 10;
+ }
+ }
+ self
+ }
+
+ fn shadow(&mut self) -> &mut Self {
+ let mut shadow = self.clone();
+ shadow.silhouette();
+ let samples = shadow.as_flat_samples_mut();
+ gaussian_blur_bytes::<4>(samples.samples, self.width() as usize, self.height() as usize, 9.0).unwrap();
+ for x in 0..shadow.width() {
+ for y in 0..shadow.height() {
+ let Rgba([r, g, b, a]) = self.get_pixel_mut(x, y);
+ if *a == 0 {
+ // SAFETY: yes
+ let p = unsafe { shadow.unsafe_get_pixel(x, y) };
+ *r = p[0];
+ *g = p[0];
+ *b = p[0];
+ *a = p[1];
+ }
+ }
+ }
+ self
+ }
}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 4d12b15..b9aa815 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,2 +1,3 @@
+pub mod array;
pub mod image;
pub use self::image::ImageUtils;
diff --git a/tmp.png b/tmp.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tmp.png