tangled
alpha
login
or
join now
brwr.dev
/
crawlspace
0
fork
atom
a bare-bones limbo server in rust (mirror of https://github.com/xoogware/crawlspace)
0
fork
atom
overview
issues
pulls
pipelines
feat(world): basic block entity parsing from anvil
brwr.dev
1 year ago
f22d95a3
b2ea8556
verified
This commit was signed with the committer's
known signature
.
brwr.dev
SSH Key Fingerprint:
SHA256:1CMSUJyAOQ8YZqYj02ZbAtxBTEOTNMiklFfu31/46wg=
+413
-5
2 changed files
expand all
collapse all
unified
split
src
world
block_entity.rs
mod.rs
+405
src/world/block_entity.rs
reviewed
···
1
1
+
/*
2
2
+
* Copyright (c) 2024 Andrew Brower.
3
3
+
* This file is part of Crawlspace.
4
4
+
*
5
5
+
* Crawlspace is free software: you can redistribute it and/or
6
6
+
* modify it under the terms of the GNU Affero General Public
7
7
+
* License as published by the Free Software Foundation, either
8
8
+
* version 3 of the License, or (at your option) any later version.
9
9
+
*
10
10
+
* Crawlspace is distributed in the hope that it will be useful,
11
11
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
13
+
* Affero General Public License for more details.
14
14
+
*
15
15
+
* You should have received a copy of the GNU Affero General Public
16
16
+
* License along with Crawlspace. If not, see
17
17
+
* <https://www.gnu.org/licenses/>.
18
18
+
*/
19
19
+
20
20
+
use std::collections::HashMap;
21
21
+
22
22
+
use color_eyre::eyre::{bail, Error, Result};
23
23
+
use fastnbt::Value;
24
24
+
25
25
+
macro_rules! get_tag {
26
26
+
($data:expr, $tag_kind:path, $tag_name:literal) => {{
27
27
+
{
28
28
+
let tag = $data.get($tag_name);
29
29
+
30
30
+
let Some(tag) = tag else {
31
31
+
bail!("Unable to find tag {}: {:?}", $tag_name, $data);
32
32
+
};
33
33
+
34
34
+
match tag {
35
35
+
$tag_kind(v) => v.to_owned(),
36
36
+
e => bail!(
37
37
+
"Improper tag kind found searching for {}: {:?}",
38
38
+
$tag_name,
39
39
+
e
40
40
+
),
41
41
+
}
42
42
+
}
43
43
+
}};
44
44
+
}
45
45
+
46
46
+
macro_rules! get_tag_opt {
47
47
+
($data:expr, $tag_kind:path, $tag_name:literal) => {{
48
48
+
let tag = $data.get($tag_name);
49
49
+
50
50
+
match tag {
51
51
+
Some($tag_kind(v)) => Some(v.to_owned()),
52
52
+
_ => None,
53
53
+
}
54
54
+
}};
55
55
+
}
56
56
+
57
57
+
pub struct BlockEntity {
58
58
+
pub id: String,
59
59
+
pub keep_packed: bool,
60
60
+
pub x: i32,
61
61
+
pub y: i32,
62
62
+
pub z: i32,
63
63
+
pub other_tags: Box<dyn BlockEntityTags>,
64
64
+
}
65
65
+
66
66
+
impl BlockEntity {
67
67
+
pub fn try_parse(value: Value) -> Result<Self> {
68
68
+
let Value::Compound(data) = value else {
69
69
+
bail!(
70
70
+
"try_parse was called with a value that is not a compound: {:?}",
71
71
+
value
72
72
+
);
73
73
+
};
74
74
+
75
75
+
let id = get_tag!(data, Value::String, "id");
76
76
+
trace!("Parsing {id}");
77
77
+
78
78
+
let other_tags: Box<dyn BlockEntityTags> = match id.as_str() {
79
79
+
"minecraft:barrel" => Box::new(BarrelBlockEntityTags::try_parse(&data)?),
80
80
+
"minecraft:brewing_stand" => Box::new(BrewingStandBlockEntityTags::try_parse(&data)?),
81
81
+
"minecraft:campfire" => Box::new(CampfireBlockEntityTags::try_parse(&data)?),
82
82
+
"minecraft:chiseled_bookshelf" => {
83
83
+
Box::new(ChiseledBookshelfBlockEntityTags::try_parse(&data)?)
84
84
+
}
85
85
+
"minecraft:sign" => Box::new(SignBlockEntityTags::try_parse(&data)?),
86
86
+
"minecraft:skull" => Box::new(SkullBlockEntityTags::try_parse(&data)?),
87
87
+
t => unimplemented!("Got block with tag {t}, but it isn't implemented"),
88
88
+
};
89
89
+
90
90
+
Ok(Self {
91
91
+
id,
92
92
+
keep_packed: get_tag!(data, Value::String, "keepPacked") == "true",
93
93
+
x: get_tag!(data, Value::Int, "x"),
94
94
+
y: get_tag!(data, Value::Int, "y"),
95
95
+
z: get_tag!(data, Value::Int, "z"),
96
96
+
other_tags,
97
97
+
})
98
98
+
}
99
99
+
}
100
100
+
101
101
+
pub struct BlockEntityItem {
102
102
+
pub slot: i8,
103
103
+
pub id: String,
104
104
+
pub count: i32,
105
105
+
}
106
106
+
107
107
+
pub trait BlockEntityTags {
108
108
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
109
109
+
where
110
110
+
Self: Sized;
111
111
+
}
112
112
+
113
113
+
pub struct SignBlockEntityTags {
114
114
+
pub is_waxed: bool,
115
115
+
pub front_text: SignText,
116
116
+
pub back_text: SignText,
117
117
+
}
118
118
+
119
119
+
pub struct SignText {
120
120
+
pub has_glowing_text: bool,
121
121
+
pub color: String,
122
122
+
pub messages: Vec<String>,
123
123
+
}
124
124
+
125
125
+
fn byte_to_bool(byte: i8) -> bool {
126
126
+
matches!(byte, 0)
127
127
+
}
128
128
+
129
129
+
impl BlockEntityTags for SignBlockEntityTags {
130
130
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self> {
131
131
+
let front_text_tag = get_tag!(from, Value::Compound, "front_text");
132
132
+
let back_text_tag = get_tag!(from, Value::Compound, "back_text");
133
133
+
134
134
+
let front_messages = get_tag!(front_text_tag, Value::List, "messages")
135
135
+
.iter()
136
136
+
.map(|v| match v {
137
137
+
Value::String(s) => s.to_owned(),
138
138
+
_ => "Unknown".to_owned(),
139
139
+
})
140
140
+
.collect();
141
141
+
142
142
+
let back_messages = get_tag!(back_text_tag, Value::List, "messages")
143
143
+
.iter()
144
144
+
.map(|v| match v {
145
145
+
Value::String(s) => s.to_owned(),
146
146
+
_ => "Unknown".to_owned(),
147
147
+
})
148
148
+
.collect();
149
149
+
150
150
+
Ok(Self {
151
151
+
is_waxed: byte_to_bool(get_tag!(from, Value::Byte, "is_waxed")),
152
152
+
front_text: SignText {
153
153
+
has_glowing_text: byte_to_bool(get_tag!(
154
154
+
front_text_tag,
155
155
+
Value::Byte,
156
156
+
"has_glowing_text"
157
157
+
)),
158
158
+
color: get_tag!(front_text_tag, Value::String, "color").to_owned(),
159
159
+
messages: front_messages,
160
160
+
},
161
161
+
back_text: SignText {
162
162
+
has_glowing_text: byte_to_bool(get_tag!(
163
163
+
back_text_tag,
164
164
+
Value::Byte,
165
165
+
"has_glowing_text"
166
166
+
)),
167
167
+
color: get_tag!(back_text_tag, Value::String, "color").to_owned(),
168
168
+
messages: back_messages,
169
169
+
},
170
170
+
})
171
171
+
}
172
172
+
}
173
173
+
174
174
+
pub struct CampfireBlockEntityTags {
175
175
+
cooking_times: Vec<i32>,
176
176
+
cooking_total_times: Vec<i32>,
177
177
+
items: Vec<BlockEntityItem>,
178
178
+
}
179
179
+
180
180
+
impl BlockEntityTags for CampfireBlockEntityTags {
181
181
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
182
182
+
where
183
183
+
Self: Sized,
184
184
+
{
185
185
+
let cooking_times = get_tag!(from, Value::List, "CookingTimes")
186
186
+
.iter()
187
187
+
.map(|v| match v {
188
188
+
Value::Int(s) => s.to_owned(),
189
189
+
_ => 0,
190
190
+
})
191
191
+
.collect();
192
192
+
193
193
+
let cooking_total_times = get_tag!(from, Value::List, "CookingTotalTimes")
194
194
+
.iter()
195
195
+
.map(|v| match v {
196
196
+
Value::Int(s) => s.to_owned(),
197
197
+
_ => 0,
198
198
+
})
199
199
+
.collect();
200
200
+
201
201
+
let items = {
202
202
+
let item_compounds = get_tag!(from, Value::List, "Items");
203
203
+
let mut items = Vec::with_capacity(item_compounds.len());
204
204
+
for c in item_compounds {
205
205
+
match c {
206
206
+
Value::Compound(c) => items.push(BlockEntityItem {
207
207
+
slot: get_tag!(c, Value::Byte, "Slot"),
208
208
+
id: get_tag!(c, Value::String, "id"),
209
209
+
count: get_tag!(c, Value::Int, "count"),
210
210
+
}),
211
211
+
t => bail!("Wrong tag type parsing item: {:?}", t),
212
212
+
}
213
213
+
}
214
214
+
items
215
215
+
};
216
216
+
217
217
+
Ok(Self {
218
218
+
cooking_times,
219
219
+
cooking_total_times,
220
220
+
items,
221
221
+
})
222
222
+
}
223
223
+
}
224
224
+
225
225
+
pub struct ChiseledBookshelfBlockEntityTags {
226
226
+
items: Vec<BlockEntityItem>,
227
227
+
last_interacted_slot: i32,
228
228
+
}
229
229
+
230
230
+
impl BlockEntityTags for ChiseledBookshelfBlockEntityTags {
231
231
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
232
232
+
where
233
233
+
Self: Sized,
234
234
+
{
235
235
+
let items = {
236
236
+
let item_compounds = get_tag!(from, Value::List, "Items");
237
237
+
let mut items = Vec::with_capacity(item_compounds.len());
238
238
+
for c in item_compounds {
239
239
+
match c {
240
240
+
Value::Compound(c) => items.push(BlockEntityItem {
241
241
+
slot: get_tag!(c, Value::Byte, "Slot"),
242
242
+
id: get_tag!(c, Value::String, "id"),
243
243
+
count: get_tag!(c, Value::Int, "count"),
244
244
+
}),
245
245
+
t => bail!("Wrong tag type parsing item: {:?}", t),
246
246
+
}
247
247
+
}
248
248
+
items
249
249
+
};
250
250
+
251
251
+
Ok(Self {
252
252
+
items,
253
253
+
last_interacted_slot: get_tag!(from, Value::Int, "last_interacted_slot"),
254
254
+
})
255
255
+
}
256
256
+
}
257
257
+
258
258
+
pub struct BarrelBlockEntityTags {
259
259
+
custom_name: Option<String>,
260
260
+
items: Vec<BlockEntityItem>,
261
261
+
}
262
262
+
263
263
+
impl BlockEntityTags for BarrelBlockEntityTags {
264
264
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
265
265
+
where
266
266
+
Self: Sized,
267
267
+
{
268
268
+
let items = {
269
269
+
let item_compounds = get_tag!(from, Value::List, "Items");
270
270
+
let mut items = Vec::with_capacity(item_compounds.len());
271
271
+
for c in item_compounds {
272
272
+
match c {
273
273
+
Value::Compound(c) => items.push(BlockEntityItem {
274
274
+
slot: get_tag!(c, Value::Byte, "Slot"),
275
275
+
id: get_tag!(c, Value::String, "id"),
276
276
+
count: get_tag!(c, Value::Int, "count"),
277
277
+
}),
278
278
+
t => bail!("Wrong tag type parsing item: {:?}", t),
279
279
+
}
280
280
+
}
281
281
+
items
282
282
+
};
283
283
+
284
284
+
Ok(Self {
285
285
+
custom_name: get_tag_opt!(from, Value::String, "CustomName"),
286
286
+
items,
287
287
+
})
288
288
+
}
289
289
+
}
290
290
+
291
291
+
pub struct BrewingStandBlockEntityTags {
292
292
+
brew_time: i16,
293
293
+
custom_name: Option<String>,
294
294
+
fuel: i8,
295
295
+
items: Vec<BlockEntityItem>,
296
296
+
lock: String,
297
297
+
}
298
298
+
299
299
+
impl BlockEntityTags for BrewingStandBlockEntityTags {
300
300
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
301
301
+
where
302
302
+
Self: Sized,
303
303
+
{
304
304
+
let items = {
305
305
+
let item_compounds = get_tag!(from, Value::List, "Items");
306
306
+
let mut items = Vec::with_capacity(item_compounds.len());
307
307
+
for c in item_compounds {
308
308
+
match c {
309
309
+
Value::Compound(c) => items.push(BlockEntityItem {
310
310
+
slot: get_tag!(c, Value::Byte, "Slot"),
311
311
+
id: get_tag!(c, Value::String, "id"),
312
312
+
count: get_tag!(c, Value::Int, "count"),
313
313
+
}),
314
314
+
t => bail!("Wrong tag type parsing item: {:?}", t),
315
315
+
}
316
316
+
}
317
317
+
items
318
318
+
};
319
319
+
320
320
+
Ok(Self {
321
321
+
brew_time: get_tag!(from, Value::Short, "BrewTime"),
322
322
+
custom_name: get_tag_opt!(from, Value::String, "CustomName"),
323
323
+
fuel: get_tag!(from, Value::Byte, "Fuel"),
324
324
+
items,
325
325
+
lock: get_tag!(from, Value::String, "Lock"),
326
326
+
})
327
327
+
}
328
328
+
}
329
329
+
330
330
+
pub struct SkullBlockEntityTags {
331
331
+
pub custom_name: Option<String>,
332
332
+
pub note_block_sound: Option<String>,
333
333
+
pub profile: SkullProfile,
334
334
+
}
335
335
+
336
336
+
pub enum SkullProfile {
337
337
+
String(String),
338
338
+
Compound {
339
339
+
name: String,
340
340
+
id: Vec<i32>,
341
341
+
properties: Option<Vec<SkullProperty>>,
342
342
+
},
343
343
+
}
344
344
+
345
345
+
pub struct SkullProperty {
346
346
+
pub name: String,
347
347
+
pub value: String,
348
348
+
pub signature: Option<String>,
349
349
+
}
350
350
+
351
351
+
impl BlockEntityTags for SkullBlockEntityTags {
352
352
+
fn try_parse(from: &HashMap<String, Value>) -> Result<Self>
353
353
+
where
354
354
+
Self: Sized,
355
355
+
{
356
356
+
let profile = match get_tag_opt!(from, Value::String, "profile") {
357
357
+
Some(s) => SkullProfile::String(s),
358
358
+
None => match get_tag_opt!(from, Value::Compound, "profile") {
359
359
+
None => bail!("Profile not present for skull"),
360
360
+
Some(c) => {
361
361
+
let id = get_tag!(from, Value::List, "id")
362
362
+
.iter()
363
363
+
.map(|v| match v {
364
364
+
Value::Int(s) => s.to_owned(),
365
365
+
_ => 0,
366
366
+
})
367
367
+
.collect();
368
368
+
369
369
+
let properties = {
370
370
+
match get_tag_opt!(from, Value::List, "properties") {
371
371
+
None => None,
372
372
+
Some(property_compounds) => {
373
373
+
let mut properties = Vec::with_capacity(property_compounds.len());
374
374
+
for c in property_compounds {
375
375
+
match c {
376
376
+
Value::Compound(s) => properties.push(SkullProperty {
377
377
+
name: get_tag!(s, Value::String, "name"),
378
378
+
value: get_tag!(s, Value::String, "value"),
379
379
+
signature: get_tag_opt!(s, Value::String, "signature"),
380
380
+
}),
381
381
+
t => bail!("Wrong tag type parsing item: {:?}", t),
382
382
+
}
383
383
+
}
384
384
+
385
385
+
Some(properties)
386
386
+
}
387
387
+
}
388
388
+
};
389
389
+
390
390
+
SkullProfile::Compound {
391
391
+
name: get_tag!(c, Value::String, "name"),
392
392
+
id,
393
393
+
properties,
394
394
+
}
395
395
+
}
396
396
+
},
397
397
+
};
398
398
+
399
399
+
Ok(Self {
400
400
+
custom_name: get_tag_opt!(from, Value::String, "custom_name"),
401
401
+
note_block_sound: get_tag_opt!(from, Value::String, "note_block_sound"),
402
402
+
profile,
403
403
+
})
404
404
+
}
405
405
+
}
+8
-5
src/world/mod.rs
reviewed
···
17
17
* <https://www.gnu.org/licenses/>.
18
18
*/
19
19
20
20
-
use std::{
21
21
-
collections::HashMap,
22
22
-
fs::File,
23
23
-
path::Path,
24
24
-
};
20
20
+
use std::{collections::HashMap, fs::File, path::Path};
25
21
22
22
+
use block_entity::BlockEntity;
26
23
use color_eyre::eyre::Result;
27
24
use fastanvil::Region;
28
25
use rayon::prelude::*;
29
26
use serde::Deserialize;
30
27
28
28
+
mod block_entity;
31
29
pub mod blocks;
32
30
33
31
#[derive(Clone, Debug)]
···
48
46
#[serde(rename = "LastUpdate")]
49
47
pub _last_update: f64,
50
48
pub sections: Vec<Section>,
49
49
+
pub block_entities: Vec<fastnbt::Value>,
51
50
}
52
51
53
52
#[derive(Clone, Debug, Deserialize)]
···
138
137
139
138
if (-10..10).contains(&parsed.x_pos) && (-10..10).contains(&parsed.z_pos) {
140
139
parsed.sections.sort_by_key(|c| c.y);
140
140
+
141
141
+
parsed.block_entities.clone().into_iter().for_each(|e| {
142
142
+
let _ = BlockEntity::try_parse(e);
143
143
+
});
141
144
142
145
debug!(
143
146
"Successfully parsed chunk at {}, {}",