1use std::io::{Cursor, Read, Write};
16
17use crate::gff_helpers::{
18 get_bool, get_f32, get_i32, get_locstring, get_resref, get_string, get_u32, get_u8,
19 upsert_field,
20};
21use crate::shared::GitTriggerPoint;
22use rakata_core::{ResRef, StrRef};
23use rakata_formats::{
24 gff_schema::{FieldSchema, GffSchema, GffType},
25 read_gff, read_gff_from_bytes, write_gff, Gff, GffBinaryError, GffLocalizedString, GffStruct,
26 GffValue,
27};
28use thiserror::Error;
29
30#[derive(Debug, Clone, PartialEq, Default)]
36pub struct GitCreature {
37 pub template_resref: ResRef,
39 pub x_position: f32,
41 pub y_position: f32,
43 pub z_position: f32,
45 pub x_orientation: f32,
47 pub y_orientation: f32,
49 pub z_orientation: f32,
51 pub object_id: u32,
53}
54
55impl GitCreature {
56 fn from_gff_struct(s: &GffStruct) -> Self {
57 Self {
58 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
59 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
60 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
61 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
62 x_orientation: get_f32(s, "XOrientation").unwrap_or(0.0),
63 y_orientation: get_f32(s, "YOrientation").unwrap_or(0.0),
64 z_orientation: get_f32(s, "ZOrientation").unwrap_or(0.0),
65 object_id: get_u32(s, "ObjectId").unwrap_or(0),
66 }
67 }
68
69 fn to_gff_struct(&self) -> GffStruct {
70 let mut s = GffStruct::new(4);
71 upsert_field(
72 &mut s,
73 "TemplateResRef",
74 GffValue::ResRef(self.template_resref),
75 );
76 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
77 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
78 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
79 upsert_field(&mut s, "XOrientation", GffValue::Single(self.x_orientation));
80 upsert_field(&mut s, "YOrientation", GffValue::Single(self.y_orientation));
81 upsert_field(&mut s, "ZOrientation", GffValue::Single(self.z_orientation));
82 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
83 s
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Default)]
89pub struct GitItem {
90 pub template_resref: ResRef,
92 pub x_position: f32,
94 pub y_position: f32,
96 pub z_position: f32,
98 pub x_orientation: f32,
100 pub y_orientation: f32,
102 pub z_orientation: f32,
104 pub object_id: u32,
106}
107
108impl GitItem {
109 fn from_gff_struct(s: &GffStruct) -> Self {
110 Self {
111 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
112 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
113 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
114 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
115 x_orientation: get_f32(s, "XOrientation").unwrap_or(0.0),
116 y_orientation: get_f32(s, "YOrientation").unwrap_or(0.0),
117 z_orientation: get_f32(s, "ZOrientation").unwrap_or(0.0),
118 object_id: get_u32(s, "ObjectId").unwrap_or(0),
119 }
120 }
121
122 fn to_gff_struct(&self) -> GffStruct {
123 let mut s = GffStruct::new(0);
124 upsert_field(
125 &mut s,
126 "TemplateResRef",
127 GffValue::ResRef(self.template_resref),
128 );
129 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
130 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
131 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
132 upsert_field(&mut s, "XOrientation", GffValue::Single(self.x_orientation));
133 upsert_field(&mut s, "YOrientation", GffValue::Single(self.y_orientation));
134 upsert_field(&mut s, "ZOrientation", GffValue::Single(self.z_orientation));
135 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
136 s
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Default)]
146pub struct GitDoor {
147 pub bearing: f32,
149 pub x: f32,
151 pub y: f32,
153 pub z: f32,
155 pub object_id: u32,
157}
158
159impl GitDoor {
160 fn from_gff_struct(s: &GffStruct) -> Self {
161 Self {
162 bearing: get_f32(s, "Bearing").unwrap_or(0.0),
163 x: get_f32(s, "X").unwrap_or(0.0),
164 y: get_f32(s, "Y").unwrap_or(0.0),
165 z: get_f32(s, "Z").unwrap_or(0.0),
166 object_id: get_u32(s, "ObjectId").unwrap_or(0),
167 }
168 }
169
170 fn to_gff_struct(&self) -> GffStruct {
171 let mut s = GffStruct::new(8);
172 upsert_field(&mut s, "Bearing", GffValue::Single(self.bearing));
173 upsert_field(&mut s, "X", GffValue::Single(self.x));
174 upsert_field(&mut s, "Y", GffValue::Single(self.y));
175 upsert_field(&mut s, "Z", GffValue::Single(self.z));
176 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
177 s
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Default)]
183pub struct GitPlaceable {
184 pub template_resref: ResRef,
186 pub bearing: f32,
188 pub x: f32,
190 pub y: f32,
192 pub z: f32,
194 pub object_id: u32,
196}
197
198impl GitPlaceable {
199 fn from_gff_struct(s: &GffStruct) -> Self {
200 Self {
201 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
202 bearing: get_f32(s, "Bearing").unwrap_or(0.0),
203 x: get_f32(s, "X").unwrap_or(0.0),
204 y: get_f32(s, "Y").unwrap_or(0.0),
205 z: get_f32(s, "Z").unwrap_or(0.0),
206 object_id: get_u32(s, "ObjectId").unwrap_or(0),
207 }
208 }
209
210 fn to_gff_struct(&self) -> GffStruct {
211 let mut s = GffStruct::new(9);
212 upsert_field(
213 &mut s,
214 "TemplateResRef",
215 GffValue::ResRef(self.template_resref),
216 );
217 upsert_field(&mut s, "Bearing", GffValue::Single(self.bearing));
218 upsert_field(&mut s, "X", GffValue::Single(self.x));
219 upsert_field(&mut s, "Y", GffValue::Single(self.y));
220 upsert_field(&mut s, "Z", GffValue::Single(self.z));
221 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
222 s
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Default)]
231pub struct GitWaypoint {
232 pub x_position: f32,
234 pub y_position: f32,
236 pub z_position: f32,
238 pub object_id: u32,
240}
241
242impl GitWaypoint {
243 fn from_gff_struct(s: &GffStruct) -> Self {
244 Self {
245 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
246 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
247 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
248 object_id: get_u32(s, "ObjectId").unwrap_or(0),
249 }
250 }
251
252 fn to_gff_struct(&self) -> GffStruct {
253 let mut s = GffStruct::new(5);
254 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
255 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
256 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
257 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
258 s
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Default)]
264pub struct GitSound {
265 pub template_resref: ResRef,
267 pub generated_type: u32,
269 pub x_position: f32,
271 pub y_position: f32,
273 pub z_position: f32,
275 pub object_id: u32,
277}
278
279impl GitSound {
280 fn from_gff_struct(s: &GffStruct) -> Self {
281 Self {
282 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
283 generated_type: get_u32(s, "GeneratedType").unwrap_or(0),
284 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
285 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
286 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
287 object_id: get_u32(s, "ObjectId").unwrap_or(0),
288 }
289 }
290
291 fn to_gff_struct(&self) -> GffStruct {
292 let mut s = GffStruct::new(6);
293 upsert_field(
294 &mut s,
295 "TemplateResRef",
296 GffValue::ResRef(self.template_resref),
297 );
298 upsert_field(
299 &mut s,
300 "GeneratedType",
301 GffValue::UInt32(self.generated_type),
302 );
303 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
304 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
305 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
306 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
307 s
308 }
309}
310
311#[derive(Debug, Clone, PartialEq)]
317pub struct GitTrigger {
318 pub template_resref: ResRef,
320 pub linked_to_module: ResRef,
322 pub transition_destination: GffLocalizedString,
325 pub linked_to: String,
327 pub linked_to_flags: u8,
329 pub x_position: f32,
331 pub y_position: f32,
333 pub z_position: f32,
335 pub geometry: Vec<GitTriggerPoint>,
337 pub object_id: u32,
339}
340
341impl Default for GitTrigger {
342 fn default() -> Self {
343 Self {
344 template_resref: ResRef::blank(),
345 linked_to_module: ResRef::blank(),
346 transition_destination: GffLocalizedString::new(StrRef::invalid()),
347 linked_to: String::new(),
348 linked_to_flags: 0,
349 x_position: 0.0,
350 y_position: 0.0,
351 z_position: 0.0,
352 geometry: Vec::new(),
353 object_id: 0,
354 }
355 }
356}
357
358impl GitTrigger {
359 fn from_gff_struct(s: &GffStruct) -> Self {
360 let geometry = match s.field("Geometry") {
361 Some(GffValue::List(elements)) => elements
362 .iter()
363 .map(GitTriggerPoint::from_gff_struct)
364 .collect(),
365 _ => Vec::new(),
366 };
367
368 Self {
369 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
370 linked_to_module: get_resref(s, "LinkedToModule").unwrap_or_default(),
371 transition_destination: get_locstring(s, "TransitionDestin")
372 .cloned()
373 .unwrap_or_else(|| GffLocalizedString::new(StrRef::invalid())),
374 linked_to: get_string(s, "LinkedTo").unwrap_or_default(),
375 linked_to_flags: get_u8(s, "LinkedToFlags").unwrap_or(0),
376 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
377 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
378 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
379 geometry,
380 object_id: get_u32(s, "ObjectId").unwrap_or(0),
381 }
382 }
383
384 fn to_gff_struct(&self) -> GffStruct {
385 let mut s = GffStruct::new(1);
386 upsert_field(
387 &mut s,
388 "TemplateResRef",
389 GffValue::ResRef(self.template_resref),
390 );
391 upsert_field(
392 &mut s,
393 "LinkedToModule",
394 GffValue::ResRef(self.linked_to_module),
395 );
396 upsert_field(
397 &mut s,
398 "TransitionDestin",
399 GffValue::LocalizedString(self.transition_destination.clone()),
400 );
401 upsert_field(&mut s, "LinkedTo", GffValue::String(self.linked_to.clone()));
402 upsert_field(
403 &mut s,
404 "LinkedToFlags",
405 GffValue::UInt8(self.linked_to_flags),
406 );
407 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
408 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
409 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
410 let geom_structs: Vec<GffStruct> =
411 self.geometry.iter().map(|p| p.to_gff_struct()).collect();
412 upsert_field(&mut s, "Geometry", GffValue::List(geom_structs));
413 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
414 s
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, Default)]
422pub struct GitStore {
423 pub resref: ResRef,
425 pub x_orientation: f32,
427 pub y_orientation: f32,
429 pub z_orientation: f32,
431 pub x_position: f32,
433 pub y_position: f32,
435 pub z_position: f32,
437 pub object_id: u32,
439}
440
441impl GitStore {
442 fn from_gff_struct(s: &GffStruct) -> Self {
443 Self {
444 resref: get_resref(s, "ResRef").unwrap_or_default(),
445 x_orientation: get_f32(s, "XOrientation").unwrap_or(0.0),
446 y_orientation: get_f32(s, "YOrientation").unwrap_or(0.0),
447 z_orientation: get_f32(s, "ZOrientation").unwrap_or(0.0),
448 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
449 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
450 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
451 object_id: get_u32(s, "ObjectId").unwrap_or(0),
452 }
453 }
454
455 fn to_gff_struct(&self) -> GffStruct {
456 let mut s = GffStruct::new(11);
457 upsert_field(&mut s, "ResRef", GffValue::ResRef(self.resref));
458 upsert_field(&mut s, "XOrientation", GffValue::Single(self.x_orientation));
459 upsert_field(&mut s, "YOrientation", GffValue::Single(self.y_orientation));
460 upsert_field(&mut s, "ZOrientation", GffValue::Single(self.z_orientation));
461 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
462 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
463 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
464 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
465 s
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Default)]
471pub struct GitEncounterPoint {
472 pub x: f32,
474 pub y: f32,
476 pub z: f32,
478}
479
480impl GitEncounterPoint {
481 fn from_gff_struct(s: &GffStruct) -> Self {
482 Self {
483 x: get_f32(s, "X").unwrap_or(0.0),
484 y: get_f32(s, "Y").unwrap_or(0.0),
485 z: get_f32(s, "Z").unwrap_or(0.0),
486 }
487 }
488
489 fn to_gff_struct(&self) -> GffStruct {
490 let mut s = GffStruct::new(0);
491 upsert_field(&mut s, "X", GffValue::Single(self.x));
492 upsert_field(&mut s, "Y", GffValue::Single(self.y));
493 upsert_field(&mut s, "Z", GffValue::Single(self.z));
494 s
495 }
496}
497
498#[derive(Debug, Clone, PartialEq, Default)]
500pub struct GitSpawnPoint {
501 pub x: f32,
503 pub y: f32,
505 pub z: f32,
507 pub orientation: f32,
509}
510
511impl GitSpawnPoint {
512 fn from_gff_struct(s: &GffStruct) -> Self {
513 Self {
514 x: get_f32(s, "X").unwrap_or(0.0),
515 y: get_f32(s, "Y").unwrap_or(0.0),
516 z: get_f32(s, "Z").unwrap_or(0.0),
517 orientation: get_f32(s, "Orientation").unwrap_or(0.0),
518 }
519 }
520
521 fn to_gff_struct(&self) -> GffStruct {
522 let mut s = GffStruct::new(0);
523 upsert_field(&mut s, "X", GffValue::Single(self.x));
524 upsert_field(&mut s, "Y", GffValue::Single(self.y));
525 upsert_field(&mut s, "Z", GffValue::Single(self.z));
526 upsert_field(&mut s, "Orientation", GffValue::Single(self.orientation));
527 s
528 }
529}
530
531#[derive(Debug, Clone, PartialEq, Default)]
533pub struct GitEncounter {
534 pub template_resref: ResRef,
536 pub x_position: f32,
538 pub y_position: f32,
540 pub z_position: f32,
542 pub geometry: Vec<GitEncounterPoint>,
544 pub spawn_points: Vec<GitSpawnPoint>,
546 pub object_id: u32,
548}
549
550impl GitEncounter {
551 fn from_gff_struct(s: &GffStruct) -> Self {
552 let geometry = match s.field("Geometry") {
553 Some(GffValue::List(elements)) => elements
554 .iter()
555 .map(GitEncounterPoint::from_gff_struct)
556 .collect(),
557 _ => Vec::new(),
558 };
559
560 let spawn_points = match s.field("SpawnPointList") {
561 Some(GffValue::List(elements)) => elements
562 .iter()
563 .map(GitSpawnPoint::from_gff_struct)
564 .collect(),
565 _ => Vec::new(),
566 };
567
568 Self {
569 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
570 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
571 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
572 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
573 geometry,
574 spawn_points,
575 object_id: get_u32(s, "ObjectId").unwrap_or(0),
576 }
577 }
578
579 fn to_gff_struct(&self) -> GffStruct {
580 let mut s = GffStruct::new(7);
581 upsert_field(
582 &mut s,
583 "TemplateResRef",
584 GffValue::ResRef(self.template_resref),
585 );
586 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
587 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
588 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
589 let geom_structs: Vec<GffStruct> =
590 self.geometry.iter().map(|p| p.to_gff_struct()).collect();
591 upsert_field(&mut s, "Geometry", GffValue::List(geom_structs));
592 let spawn_structs: Vec<GffStruct> = self
593 .spawn_points
594 .iter()
595 .map(|sp| sp.to_gff_struct())
596 .collect();
597 upsert_field(&mut s, "SpawnPointList", GffValue::List(spawn_structs));
598 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
599 s
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Default)]
608pub struct GitAreaEffect {
609 pub orientation_x: f32,
611 pub orientation_y: f32,
613 pub orientation_z: f32,
615 pub position_x: f32,
617 pub position_y: f32,
619 pub position_z: f32,
621 pub object_id: u32,
623}
624
625impl GitAreaEffect {
626 fn from_gff_struct(s: &GffStruct) -> Self {
627 Self {
628 orientation_x: get_f32(s, "OrientationX").unwrap_or(0.0),
629 orientation_y: get_f32(s, "OrientationY").unwrap_or(0.0),
630 orientation_z: get_f32(s, "OrientationZ").unwrap_or(0.0),
631 position_x: get_f32(s, "PositionX").unwrap_or(0.0),
632 position_y: get_f32(s, "PositionY").unwrap_or(0.0),
633 position_z: get_f32(s, "PositionZ").unwrap_or(0.0),
634 object_id: get_u32(s, "ObjectId").unwrap_or(0),
635 }
636 }
637
638 fn to_gff_struct(&self) -> GffStruct {
639 let mut s = GffStruct::new(13);
640 upsert_field(&mut s, "OrientationX", GffValue::Single(self.orientation_x));
641 upsert_field(&mut s, "OrientationY", GffValue::Single(self.orientation_y));
642 upsert_field(&mut s, "OrientationZ", GffValue::Single(self.orientation_z));
643 upsert_field(&mut s, "PositionX", GffValue::Single(self.position_x));
644 upsert_field(&mut s, "PositionY", GffValue::Single(self.position_y));
645 upsert_field(&mut s, "PositionZ", GffValue::Single(self.position_z));
646 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id));
647 s
648 }
649}
650
651#[derive(Debug, Clone, PartialEq, Default)]
659pub struct GitAreaProperties {
660 pub unescapable: bool,
662 pub stealth_xp_max: u32,
664 pub stealth_xp_current: u32,
666 pub stealth_xp_loss: u32,
668 pub stealth_xp_enabled: bool,
670 pub trans_pending: bool,
672 pub trans_pend_next_id: u8,
674 pub trans_pend_curr_id: u8,
676 pub sun_fog_color: u32,
678 pub music_delay: i32,
680 pub music_day: i32,
682 pub music_night: i32,
684 pub music_battle: i32,
686 pub ambient_snd_day: i32,
688 pub ambient_snd_night: i32,
690 pub ambient_snd_day_vol: i32,
692 pub ambient_snd_nit_vol: i32,
694}
695
696impl GitAreaProperties {
697 fn from_gff_struct(s: &GffStruct) -> Self {
698 Self {
699 unescapable: get_bool(s, "Unescapable").unwrap_or(false),
700 stealth_xp_max: get_u32(s, "StealthXPMax").unwrap_or(0),
701 stealth_xp_current: get_u32(s, "StealthXPCurrent").unwrap_or(0),
702 stealth_xp_loss: get_u32(s, "StealthXPLoss").unwrap_or(0),
703 stealth_xp_enabled: get_bool(s, "StealthXPEnabled").unwrap_or(false),
704 trans_pending: get_bool(s, "TransPending").unwrap_or(false),
705 trans_pend_next_id: get_u8(s, "TransPendNextID").unwrap_or(0),
706 trans_pend_curr_id: get_u8(s, "TransPendCurrID").unwrap_or(0),
707 sun_fog_color: get_u32(s, "SunFogColor").unwrap_or(0),
708 music_delay: get_i32(s, "MusicDelay").unwrap_or(0),
709 music_day: get_i32(s, "MusicDay").unwrap_or(0),
710 music_night: get_i32(s, "MusicNight").unwrap_or(0),
711 music_battle: get_i32(s, "MusicBattle").unwrap_or(0),
712 ambient_snd_day: get_i32(s, "AmbientSndDay").unwrap_or(0),
713 ambient_snd_night: get_i32(s, "AmbientSndNight").unwrap_or(0),
714 ambient_snd_day_vol: get_i32(s, "AmbientSndDayVol").unwrap_or(0),
715 ambient_snd_nit_vol: get_i32(s, "AmbientSndNitVol").unwrap_or(0),
716 }
717 }
718
719 fn to_gff_struct(&self) -> GffStruct {
720 let mut s = GffStruct::new(0);
721 upsert_field(
722 &mut s,
723 "Unescapable",
724 GffValue::UInt8(u8::from(self.unescapable)),
725 );
726 upsert_field(
727 &mut s,
728 "StealthXPMax",
729 GffValue::UInt32(self.stealth_xp_max),
730 );
731 upsert_field(
732 &mut s,
733 "StealthXPCurrent",
734 GffValue::UInt32(self.stealth_xp_current),
735 );
736 upsert_field(
737 &mut s,
738 "StealthXPLoss",
739 GffValue::UInt32(self.stealth_xp_loss),
740 );
741 upsert_field(
742 &mut s,
743 "StealthXPEnabled",
744 GffValue::UInt8(u8::from(self.stealth_xp_enabled)),
745 );
746 upsert_field(
747 &mut s,
748 "TransPending",
749 GffValue::UInt8(u8::from(self.trans_pending)),
750 );
751 upsert_field(
752 &mut s,
753 "TransPendNextID",
754 GffValue::UInt8(self.trans_pend_next_id),
755 );
756 upsert_field(
757 &mut s,
758 "TransPendCurrID",
759 GffValue::UInt8(self.trans_pend_curr_id),
760 );
761 upsert_field(&mut s, "SunFogColor", GffValue::UInt32(self.sun_fog_color));
762 upsert_field(&mut s, "MusicDelay", GffValue::Int32(self.music_delay));
763 upsert_field(&mut s, "MusicDay", GffValue::Int32(self.music_day));
764 upsert_field(&mut s, "MusicNight", GffValue::Int32(self.music_night));
765 upsert_field(&mut s, "MusicBattle", GffValue::Int32(self.music_battle));
766 upsert_field(
767 &mut s,
768 "AmbientSndDay",
769 GffValue::Int32(self.ambient_snd_day),
770 );
771 upsert_field(
772 &mut s,
773 "AmbientSndNight",
774 GffValue::Int32(self.ambient_snd_night),
775 );
776 upsert_field(
777 &mut s,
778 "AmbientSndDayVol",
779 GffValue::Int32(self.ambient_snd_day_vol),
780 );
781 upsert_field(
782 &mut s,
783 "AmbientSndNitVol",
784 GffValue::Int32(self.ambient_snd_nit_vol),
785 );
786 s
787 }
788}
789
790#[derive(Debug, Clone, PartialEq, Default)]
794pub struct GitCamera {
795 pub camera_id: i32,
797 pub position: [f32; 3],
799 pub orientation: [f32; 4],
801 pub pitch: f32,
803 pub height: f32,
805 pub field_of_view: f32,
807 pub mic_range: f32,
809}
810
811impl GitCamera {
812 fn from_gff_struct(s: &GffStruct) -> Self {
813 let position = match s.field("Position") {
814 Some(GffValue::Vector3(v)) => *v,
815 _ => [0.0, 0.0, 0.0],
816 };
817 let orientation = match s.field("Orientation") {
818 Some(GffValue::Vector4(v)) => *v,
819 _ => [0.0, 0.0, 0.0, 1.0],
820 };
821
822 Self {
823 camera_id: get_i32(s, "CameraID").unwrap_or(-1),
824 position,
825 orientation,
826 pitch: get_f32(s, "Pitch").unwrap_or(0.0),
827 height: get_f32(s, "Height").unwrap_or(0.0),
828 field_of_view: get_f32(s, "FieldOfView").unwrap_or(55.0),
829 mic_range: get_f32(s, "MicRange").unwrap_or(0.0),
830 }
831 }
832
833 fn to_gff_struct(&self) -> GffStruct {
834 let mut s = GffStruct::new(0);
835 upsert_field(&mut s, "CameraID", GffValue::Int32(self.camera_id));
836 upsert_field(&mut s, "Position", GffValue::Vector3(self.position));
837 upsert_field(&mut s, "Orientation", GffValue::Vector4(self.orientation));
838 upsert_field(&mut s, "Pitch", GffValue::Single(self.pitch));
839 upsert_field(&mut s, "Height", GffValue::Single(self.height));
840 upsert_field(&mut s, "FieldOfView", GffValue::Single(self.field_of_view));
841 upsert_field(&mut s, "MicRange", GffValue::Single(self.mic_range));
842 s
843 }
844}
845
846#[derive(Debug, Clone, PartialEq, Default)]
857pub struct Git {
858 pub current_weather: u8,
861 pub weather_started: bool,
863 pub use_templates: bool,
865
866 pub creatures: Vec<GitCreature>,
869 pub items: Vec<GitItem>,
871 pub doors: Vec<GitDoor>,
873 pub placeables: Vec<GitPlaceable>,
875 pub waypoints: Vec<GitWaypoint>,
877 pub sounds: Vec<GitSound>,
879 pub triggers: Vec<GitTrigger>,
881 pub stores: Vec<GitStore>,
883 pub encounters: Vec<GitEncounter>,
885 pub area_effects: Vec<GitAreaEffect>,
887
888 pub area_properties: Option<GitAreaProperties>,
891
892 pub cameras: Vec<GitCamera>,
895}
896
897impl Git {
898 pub fn new() -> Self {
900 Self::default()
901 }
902
903 pub fn from_gff(gff: &Gff) -> Result<Self, GitError> {
905 if gff.file_type != *b"GIT " && gff.file_type != *b"GFF " {
906 return Err(GitError::UnsupportedFileType(gff.file_type));
907 }
908
909 let root = &gff.root;
910
911 fn read_list<T>(root: &GffStruct, label: &str, f: fn(&GffStruct) -> T) -> Vec<T> {
912 match root.field(label) {
913 Some(GffValue::List(elements)) => elements.iter().map(f).collect(),
914 _ => Vec::new(),
915 }
916 }
917
918 let area_properties = match root.field("AreaProperties") {
919 Some(GffValue::Struct(s)) => Some(GitAreaProperties::from_gff_struct(s)),
920 _ => None,
921 };
922
923 Ok(Self {
924 current_weather: get_u8(root, "CurrentWeather").unwrap_or(0),
925 weather_started: get_bool(root, "WeatherStarted").unwrap_or(false),
926 use_templates: get_bool(root, "UseTemplates").unwrap_or(false),
927
928 creatures: read_list(root, "Creature List", GitCreature::from_gff_struct),
929 items: read_list(root, "List", GitItem::from_gff_struct),
930 doors: read_list(root, "Door List", GitDoor::from_gff_struct),
931 placeables: read_list(root, "Placeable List", GitPlaceable::from_gff_struct),
932 waypoints: read_list(root, "WaypointList", GitWaypoint::from_gff_struct),
933 sounds: read_list(root, "SoundList", GitSound::from_gff_struct),
934 triggers: read_list(root, "TriggerList", GitTrigger::from_gff_struct),
935 stores: read_list(root, "StoreList", GitStore::from_gff_struct),
936 encounters: read_list(root, "Encounter List", GitEncounter::from_gff_struct),
937 area_effects: read_list(root, "AreaEffectList", GitAreaEffect::from_gff_struct),
938
939 area_properties,
940 cameras: read_list(root, "CameraList", GitCamera::from_gff_struct),
941 })
942 }
943
944 pub fn to_gff(&self) -> Gff {
946 let mut root = GffStruct::new(-1);
947
948 upsert_field(
949 &mut root,
950 "CurrentWeather",
951 GffValue::UInt8(self.current_weather),
952 );
953 upsert_field(
954 &mut root,
955 "WeatherStarted",
956 GffValue::UInt8(u8::from(self.weather_started)),
957 );
958 upsert_field(
959 &mut root,
960 "UseTemplates",
961 GffValue::UInt8(u8::from(self.use_templates)),
962 );
963
964 fn write_list<T>(root: &mut GffStruct, label: &str, items: &[T], f: fn(&T) -> GffStruct) {
965 let structs: Vec<GffStruct> = items.iter().map(f).collect();
966 upsert_field(root, label, GffValue::List(structs));
967 }
968
969 write_list(
970 &mut root,
971 "Creature List",
972 &self.creatures,
973 GitCreature::to_gff_struct,
974 );
975 write_list(&mut root, "List", &self.items, GitItem::to_gff_struct);
976 write_list(&mut root, "Door List", &self.doors, GitDoor::to_gff_struct);
977 write_list(
978 &mut root,
979 "Placeable List",
980 &self.placeables,
981 GitPlaceable::to_gff_struct,
982 );
983 write_list(
984 &mut root,
985 "WaypointList",
986 &self.waypoints,
987 GitWaypoint::to_gff_struct,
988 );
989 write_list(
990 &mut root,
991 "SoundList",
992 &self.sounds,
993 GitSound::to_gff_struct,
994 );
995 write_list(
996 &mut root,
997 "TriggerList",
998 &self.triggers,
999 GitTrigger::to_gff_struct,
1000 );
1001 write_list(
1002 &mut root,
1003 "StoreList",
1004 &self.stores,
1005 GitStore::to_gff_struct,
1006 );
1007 write_list(
1008 &mut root,
1009 "Encounter List",
1010 &self.encounters,
1011 GitEncounter::to_gff_struct,
1012 );
1013 write_list(
1014 &mut root,
1015 "AreaEffectList",
1016 &self.area_effects,
1017 GitAreaEffect::to_gff_struct,
1018 );
1019
1020 if let Some(ref ap) = self.area_properties {
1021 upsert_field(
1022 &mut root,
1023 "AreaProperties",
1024 GffValue::Struct(Box::new(ap.to_gff_struct())),
1025 );
1026 }
1027
1028 write_list(
1029 &mut root,
1030 "CameraList",
1031 &self.cameras,
1032 GitCamera::to_gff_struct,
1033 );
1034
1035 Gff::new(*b"GIT ", root)
1036 }
1037}
1038
1039#[derive(Debug, Error)]
1041pub enum GitError {
1042 #[error("unsupported GIT file type: {0:?}")]
1044 UnsupportedFileType([u8; 4]),
1045 #[error(transparent)]
1047 Gff(#[from] GffBinaryError),
1048}
1049
1050#[cfg_attr(
1052 feature = "tracing",
1053 tracing::instrument(level = "debug", skip(reader))
1054)]
1055pub fn read_git<R: Read>(reader: &mut R) -> Result<Git, GitError> {
1056 let gff = read_gff(reader)?;
1057 Git::from_gff(&gff)
1058}
1059
1060#[cfg_attr(
1062 feature = "tracing",
1063 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
1064)]
1065pub fn read_git_from_bytes(bytes: &[u8]) -> Result<Git, GitError> {
1066 let gff = read_gff_from_bytes(bytes)?;
1067 Git::from_gff(&gff)
1068}
1069
1070#[cfg_attr(
1072 feature = "tracing",
1073 tracing::instrument(level = "debug", skip(writer, git))
1074)]
1075pub fn write_git<W: Write>(writer: &mut W, git: &Git) -> Result<(), GitError> {
1076 let gff = git.to_gff();
1077 write_gff(writer, &gff)?;
1078 Ok(())
1079}
1080
1081#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(git)))]
1083pub fn write_git_to_vec(git: &Git) -> Result<Vec<u8>, GitError> {
1084 let mut cursor = Cursor::new(Vec::new());
1085 write_git(&mut cursor, git)?;
1086 Ok(cursor.into_inner())
1087}
1088
1089static CREATURE_LIST_CHILDREN: &[FieldSchema] = &[
1095 FieldSchema {
1096 label: "ObjectId",
1097 expected_type: GffType::UInt32,
1098 required: false,
1099 children: None,
1100 constraint: None,
1101 },
1102 FieldSchema {
1103 label: "TemplateResRef",
1104 expected_type: GffType::ResRef,
1105 required: false,
1106 children: None,
1107 constraint: None,
1108 },
1109 FieldSchema {
1110 label: "XPosition",
1111 expected_type: GffType::Single,
1112 required: false,
1113 children: None,
1114 constraint: None,
1115 },
1116 FieldSchema {
1117 label: "YPosition",
1118 expected_type: GffType::Single,
1119 required: false,
1120 children: None,
1121 constraint: None,
1122 },
1123 FieldSchema {
1124 label: "ZPosition",
1125 expected_type: GffType::Single,
1126 required: false,
1127 children: None,
1128 constraint: None,
1129 },
1130 FieldSchema {
1131 label: "XOrientation",
1132 expected_type: GffType::Single,
1133 required: false,
1134 children: None,
1135 constraint: None,
1136 },
1137 FieldSchema {
1138 label: "YOrientation",
1139 expected_type: GffType::Single,
1140 required: false,
1141 children: None,
1142 constraint: None,
1143 },
1144 FieldSchema {
1145 label: "ZOrientation",
1146 expected_type: GffType::Single,
1147 required: false,
1148 children: None,
1149 constraint: None,
1150 },
1151];
1152
1153static ITEM_LIST_CHILDREN: &[FieldSchema] = &[
1155 FieldSchema {
1156 label: "ObjectId",
1157 expected_type: GffType::UInt32,
1158 required: false,
1159 children: None,
1160 constraint: None,
1161 },
1162 FieldSchema {
1163 label: "TemplateResRef",
1164 expected_type: GffType::ResRef,
1165 required: false,
1166 children: None,
1167 constraint: None,
1168 },
1169 FieldSchema {
1170 label: "XPosition",
1171 expected_type: GffType::Single,
1172 required: false,
1173 children: None,
1174 constraint: None,
1175 },
1176 FieldSchema {
1177 label: "YPosition",
1178 expected_type: GffType::Single,
1179 required: false,
1180 children: None,
1181 constraint: None,
1182 },
1183 FieldSchema {
1184 label: "ZPosition",
1185 expected_type: GffType::Single,
1186 required: false,
1187 children: None,
1188 constraint: None,
1189 },
1190 FieldSchema {
1191 label: "XOrientation",
1192 expected_type: GffType::Single,
1193 required: false,
1194 children: None,
1195 constraint: None,
1196 },
1197 FieldSchema {
1198 label: "YOrientation",
1199 expected_type: GffType::Single,
1200 required: false,
1201 children: None,
1202 constraint: None,
1203 },
1204 FieldSchema {
1205 label: "ZOrientation",
1206 expected_type: GffType::Single,
1207 required: false,
1208 children: None,
1209 constraint: None,
1210 },
1211];
1212
1213static DOOR_LIST_CHILDREN: &[FieldSchema] = &[
1215 FieldSchema {
1216 label: "ObjectId",
1217 expected_type: GffType::UInt32,
1218 required: false,
1219 children: None,
1220 constraint: None,
1221 },
1222 FieldSchema {
1223 label: "Bearing",
1224 expected_type: GffType::Single,
1225 required: false,
1226 children: None,
1227 constraint: None,
1228 },
1229 FieldSchema {
1230 label: "X",
1231 expected_type: GffType::Single,
1232 required: false,
1233 children: None,
1234 constraint: None,
1235 },
1236 FieldSchema {
1237 label: "Y",
1238 expected_type: GffType::Single,
1239 required: false,
1240 children: None,
1241 constraint: None,
1242 },
1243 FieldSchema {
1244 label: "Z",
1245 expected_type: GffType::Single,
1246 required: false,
1247 children: None,
1248 constraint: None,
1249 },
1250];
1251
1252static PLACEABLE_LIST_CHILDREN: &[FieldSchema] = &[
1254 FieldSchema {
1255 label: "ObjectId",
1256 expected_type: GffType::UInt32,
1257 required: false,
1258 children: None,
1259 constraint: None,
1260 },
1261 FieldSchema {
1262 label: "TemplateResRef",
1263 expected_type: GffType::ResRef,
1264 required: false,
1265 children: None,
1266 constraint: None,
1267 },
1268 FieldSchema {
1269 label: "Bearing",
1270 expected_type: GffType::Single,
1271 required: false,
1272 children: None,
1273 constraint: None,
1274 },
1275 FieldSchema {
1276 label: "X",
1277 expected_type: GffType::Single,
1278 required: false,
1279 children: None,
1280 constraint: None,
1281 },
1282 FieldSchema {
1283 label: "Y",
1284 expected_type: GffType::Single,
1285 required: false,
1286 children: None,
1287 constraint: None,
1288 },
1289 FieldSchema {
1290 label: "Z",
1291 expected_type: GffType::Single,
1292 required: false,
1293 children: None,
1294 constraint: None,
1295 },
1296];
1297
1298static WAYPOINT_LIST_CHILDREN: &[FieldSchema] = &[
1300 FieldSchema {
1301 label: "ObjectId",
1302 expected_type: GffType::UInt32,
1303 required: false,
1304 children: None,
1305 constraint: None,
1306 },
1307 FieldSchema {
1308 label: "XPosition",
1309 expected_type: GffType::Single,
1310 required: false,
1311 children: None,
1312 constraint: None,
1313 },
1314 FieldSchema {
1315 label: "YPosition",
1316 expected_type: GffType::Single,
1317 required: false,
1318 children: None,
1319 constraint: None,
1320 },
1321 FieldSchema {
1322 label: "ZPosition",
1323 expected_type: GffType::Single,
1324 required: false,
1325 children: None,
1326 constraint: None,
1327 },
1328];
1329
1330static SOUND_LIST_CHILDREN: &[FieldSchema] = &[
1332 FieldSchema {
1333 label: "ObjectId",
1334 expected_type: GffType::UInt32,
1335 required: false,
1336 children: None,
1337 constraint: None,
1338 },
1339 FieldSchema {
1340 label: "TemplateResRef",
1341 expected_type: GffType::ResRef,
1342 required: false,
1343 children: None,
1344 constraint: None,
1345 },
1346 FieldSchema {
1347 label: "GeneratedType",
1348 expected_type: GffType::UInt32,
1349 required: false,
1350 children: None,
1351 constraint: None,
1352 },
1353 FieldSchema {
1354 label: "XPosition",
1355 expected_type: GffType::Single,
1356 required: false,
1357 children: None,
1358 constraint: None,
1359 },
1360 FieldSchema {
1361 label: "YPosition",
1362 expected_type: GffType::Single,
1363 required: false,
1364 children: None,
1365 constraint: None,
1366 },
1367 FieldSchema {
1368 label: "ZPosition",
1369 expected_type: GffType::Single,
1370 required: false,
1371 children: None,
1372 constraint: None,
1373 },
1374];
1375
1376static TRIGGER_GEOMETRY_CHILDREN: &[FieldSchema] = &[
1378 FieldSchema {
1379 label: "PointX",
1380 expected_type: GffType::Single,
1381 required: false,
1382 children: None,
1383 constraint: None,
1384 },
1385 FieldSchema {
1386 label: "PointY",
1387 expected_type: GffType::Single,
1388 required: false,
1389 children: None,
1390 constraint: None,
1391 },
1392 FieldSchema {
1393 label: "PointZ",
1394 expected_type: GffType::Single,
1395 required: false,
1396 children: None,
1397 constraint: None,
1398 },
1399];
1400
1401static TRIGGER_LIST_CHILDREN: &[FieldSchema] = &[
1403 FieldSchema {
1404 label: "ObjectId",
1405 expected_type: GffType::UInt32,
1406 required: false,
1407 children: None,
1408 constraint: None,
1409 },
1410 FieldSchema {
1411 label: "TemplateResRef",
1412 expected_type: GffType::ResRef,
1413 required: false,
1414 children: None,
1415 constraint: None,
1416 },
1417 FieldSchema {
1418 label: "LinkedToModule",
1419 expected_type: GffType::ResRef,
1420 required: false,
1421 children: None,
1422 constraint: None,
1423 },
1424 FieldSchema {
1425 label: "TransitionDestin",
1426 expected_type: GffType::LocalizedString,
1427 required: false,
1428 children: None,
1429 constraint: None,
1430 },
1431 FieldSchema {
1432 label: "LinkedTo",
1433 expected_type: GffType::String,
1434 required: false,
1435 children: None,
1436 constraint: None,
1437 },
1438 FieldSchema {
1439 label: "LinkedToFlags",
1440 expected_type: GffType::UInt8,
1441 required: false,
1442 children: None,
1443 constraint: None,
1444 },
1445 FieldSchema {
1446 label: "XPosition",
1447 expected_type: GffType::Single,
1448 required: false,
1449 children: None,
1450 constraint: None,
1451 },
1452 FieldSchema {
1453 label: "YPosition",
1454 expected_type: GffType::Single,
1455 required: false,
1456 children: None,
1457 constraint: None,
1458 },
1459 FieldSchema {
1460 label: "ZPosition",
1461 expected_type: GffType::Single,
1462 required: false,
1463 children: None,
1464 constraint: None,
1465 },
1466 FieldSchema {
1467 label: "Geometry",
1468 expected_type: GffType::List,
1469 required: false,
1470 children: Some(TRIGGER_GEOMETRY_CHILDREN),
1471 constraint: None,
1472 },
1473];
1474
1475static STORE_LIST_CHILDREN: &[FieldSchema] = &[
1477 FieldSchema {
1478 label: "ObjectId",
1479 expected_type: GffType::UInt32,
1480 required: false,
1481 children: None,
1482 constraint: None,
1483 },
1484 FieldSchema {
1485 label: "ResRef",
1486 expected_type: GffType::ResRef,
1487 required: false,
1488 children: None,
1489 constraint: None,
1490 },
1491 FieldSchema {
1492 label: "XOrientation",
1493 expected_type: GffType::Single,
1494 required: false,
1495 children: None,
1496 constraint: None,
1497 },
1498 FieldSchema {
1499 label: "YOrientation",
1500 expected_type: GffType::Single,
1501 required: false,
1502 children: None,
1503 constraint: None,
1504 },
1505 FieldSchema {
1506 label: "ZOrientation",
1507 expected_type: GffType::Single,
1508 required: false,
1509 children: None,
1510 constraint: None,
1511 },
1512 FieldSchema {
1513 label: "XPosition",
1514 expected_type: GffType::Single,
1515 required: false,
1516 children: None,
1517 constraint: None,
1518 },
1519 FieldSchema {
1520 label: "YPosition",
1521 expected_type: GffType::Single,
1522 required: false,
1523 children: None,
1524 constraint: None,
1525 },
1526 FieldSchema {
1527 label: "ZPosition",
1528 expected_type: GffType::Single,
1529 required: false,
1530 children: None,
1531 constraint: None,
1532 },
1533];
1534
1535static ENCOUNTER_GEOMETRY_CHILDREN: &[FieldSchema] = &[
1537 FieldSchema {
1538 label: "X",
1539 expected_type: GffType::Single,
1540 required: false,
1541 children: None,
1542 constraint: None,
1543 },
1544 FieldSchema {
1545 label: "Y",
1546 expected_type: GffType::Single,
1547 required: false,
1548 children: None,
1549 constraint: None,
1550 },
1551 FieldSchema {
1552 label: "Z",
1553 expected_type: GffType::Single,
1554 required: false,
1555 children: None,
1556 constraint: None,
1557 },
1558];
1559
1560static ENCOUNTER_SPAWN_POINT_CHILDREN: &[FieldSchema] = &[
1562 FieldSchema {
1563 label: "X",
1564 expected_type: GffType::Single,
1565 required: false,
1566 children: None,
1567 constraint: None,
1568 },
1569 FieldSchema {
1570 label: "Y",
1571 expected_type: GffType::Single,
1572 required: false,
1573 children: None,
1574 constraint: None,
1575 },
1576 FieldSchema {
1577 label: "Z",
1578 expected_type: GffType::Single,
1579 required: false,
1580 children: None,
1581 constraint: None,
1582 },
1583 FieldSchema {
1584 label: "Orientation",
1585 expected_type: GffType::Single,
1586 required: false,
1587 children: None,
1588 constraint: None,
1589 },
1590];
1591
1592static ENCOUNTER_LIST_CHILDREN: &[FieldSchema] = &[
1594 FieldSchema {
1595 label: "ObjectId",
1596 expected_type: GffType::UInt32,
1597 required: false,
1598 children: None,
1599 constraint: None,
1600 },
1601 FieldSchema {
1602 label: "TemplateResRef",
1603 expected_type: GffType::ResRef,
1604 required: false,
1605 children: None,
1606 constraint: None,
1607 },
1608 FieldSchema {
1609 label: "XPosition",
1610 expected_type: GffType::Single,
1611 required: false,
1612 children: None,
1613 constraint: None,
1614 },
1615 FieldSchema {
1616 label: "YPosition",
1617 expected_type: GffType::Single,
1618 required: false,
1619 children: None,
1620 constraint: None,
1621 },
1622 FieldSchema {
1623 label: "ZPosition",
1624 expected_type: GffType::Single,
1625 required: false,
1626 children: None,
1627 constraint: None,
1628 },
1629 FieldSchema {
1630 label: "Geometry",
1631 expected_type: GffType::List,
1632 required: false,
1633 children: Some(ENCOUNTER_GEOMETRY_CHILDREN),
1634 constraint: None,
1635 },
1636 FieldSchema {
1637 label: "SpawnPointList",
1638 expected_type: GffType::List,
1639 required: false,
1640 children: Some(ENCOUNTER_SPAWN_POINT_CHILDREN),
1641 constraint: None,
1642 },
1643];
1644
1645static AREA_EFFECT_LIST_CHILDREN: &[FieldSchema] = &[
1647 FieldSchema {
1648 label: "ObjectId",
1649 expected_type: GffType::UInt32,
1650 required: false,
1651 children: None,
1652 constraint: None,
1653 },
1654 FieldSchema {
1655 label: "OrientationX",
1656 expected_type: GffType::Single,
1657 required: false,
1658 children: None,
1659 constraint: None,
1660 },
1661 FieldSchema {
1662 label: "OrientationY",
1663 expected_type: GffType::Single,
1664 required: false,
1665 children: None,
1666 constraint: None,
1667 },
1668 FieldSchema {
1669 label: "OrientationZ",
1670 expected_type: GffType::Single,
1671 required: false,
1672 children: None,
1673 constraint: None,
1674 },
1675 FieldSchema {
1676 label: "PositionX",
1677 expected_type: GffType::Single,
1678 required: false,
1679 children: None,
1680 constraint: None,
1681 },
1682 FieldSchema {
1683 label: "PositionY",
1684 expected_type: GffType::Single,
1685 required: false,
1686 children: None,
1687 constraint: None,
1688 },
1689 FieldSchema {
1690 label: "PositionZ",
1691 expected_type: GffType::Single,
1692 required: false,
1693 children: None,
1694 constraint: None,
1695 },
1696];
1697
1698static AREA_PROPERTIES_CHILDREN: &[FieldSchema] = &[
1700 FieldSchema {
1701 label: "Unescapable",
1702 expected_type: GffType::UInt8,
1703 required: false,
1704 children: None,
1705 constraint: None,
1706 },
1707 FieldSchema {
1708 label: "StealthXPMax",
1709 expected_type: GffType::UInt32,
1710 required: false,
1711 children: None,
1712 constraint: None,
1713 },
1714 FieldSchema {
1715 label: "StealthXPCurrent",
1716 expected_type: GffType::UInt32,
1717 required: false,
1718 children: None,
1719 constraint: None,
1720 },
1721 FieldSchema {
1722 label: "StealthXPLoss",
1723 expected_type: GffType::UInt32,
1724 required: false,
1725 children: None,
1726 constraint: None,
1727 },
1728 FieldSchema {
1729 label: "StealthXPEnabled",
1730 expected_type: GffType::UInt8,
1731 required: false,
1732 children: None,
1733 constraint: None,
1734 },
1735 FieldSchema {
1736 label: "TransPending",
1737 expected_type: GffType::UInt8,
1738 required: false,
1739 children: None,
1740 constraint: None,
1741 },
1742 FieldSchema {
1743 label: "TransPendNextID",
1744 expected_type: GffType::UInt8,
1745 required: false,
1746 children: None,
1747 constraint: None,
1748 },
1749 FieldSchema {
1750 label: "TransPendCurrID",
1751 expected_type: GffType::UInt8,
1752 required: false,
1753 children: None,
1754 constraint: None,
1755 },
1756 FieldSchema {
1757 label: "SunFogColor",
1758 expected_type: GffType::UInt32,
1759 required: false,
1760 children: None,
1761 constraint: None,
1762 },
1763 FieldSchema {
1765 label: "MusicDelay",
1766 expected_type: GffType::Int32,
1767 required: false,
1768 children: None,
1769 constraint: None,
1770 },
1771 FieldSchema {
1772 label: "MusicDay",
1773 expected_type: GffType::Int32,
1774 required: false,
1775 children: None,
1776 constraint: None,
1777 },
1778 FieldSchema {
1779 label: "MusicNight",
1780 expected_type: GffType::Int32,
1781 required: false,
1782 children: None,
1783 constraint: None,
1784 },
1785 FieldSchema {
1786 label: "MusicBattle",
1787 expected_type: GffType::Int32,
1788 required: false,
1789 children: None,
1790 constraint: None,
1791 },
1792 FieldSchema {
1793 label: "AmbientSndDay",
1794 expected_type: GffType::Int32,
1795 required: false,
1796 children: None,
1797 constraint: None,
1798 },
1799 FieldSchema {
1800 label: "AmbientSndNight",
1801 expected_type: GffType::Int32,
1802 required: false,
1803 children: None,
1804 constraint: None,
1805 },
1806 FieldSchema {
1807 label: "AmbientSndDayVol",
1808 expected_type: GffType::Int32,
1809 required: false,
1810 children: None,
1811 constraint: None,
1812 },
1813 FieldSchema {
1814 label: "AmbientSndNitVol",
1815 expected_type: GffType::Int32,
1816 required: false,
1817 children: None,
1818 constraint: None,
1819 },
1820];
1821
1822static AREA_MAP_CHILDREN: &[FieldSchema] = &[
1824 FieldSchema {
1825 label: "AreaMapResX",
1826 expected_type: GffType::Int32,
1827 required: false,
1828 children: None,
1829 constraint: None,
1830 },
1831 FieldSchema {
1832 label: "AreaMapResY",
1833 expected_type: GffType::Int32,
1834 required: false,
1835 children: None,
1836 constraint: None,
1837 },
1838 FieldSchema {
1839 label: "AreaMapDataSize",
1840 expected_type: GffType::UInt32,
1841 required: false,
1842 children: None,
1843 constraint: None,
1844 },
1845 FieldSchema {
1846 label: "AreaMapData",
1847 expected_type: GffType::Binary,
1848 required: false,
1849 children: None,
1850 constraint: None,
1851 },
1852];
1853
1854static CAMERA_LIST_CHILDREN: &[FieldSchema] = &[
1856 FieldSchema {
1857 label: "CameraID",
1858 expected_type: GffType::Int32,
1859 required: false,
1860 children: None,
1861 constraint: None,
1862 },
1863 FieldSchema {
1864 label: "Position",
1865 expected_type: GffType::Vector3,
1866 required: false,
1867 children: None,
1868 constraint: None,
1869 },
1870 FieldSchema {
1871 label: "Orientation",
1872 expected_type: GffType::Vector4,
1873 required: false,
1874 children: None,
1875 constraint: None,
1876 },
1877 FieldSchema {
1878 label: "Pitch",
1879 expected_type: GffType::Single,
1880 required: false,
1881 children: None,
1882 constraint: None,
1883 },
1884 FieldSchema {
1885 label: "Height",
1886 expected_type: GffType::Single,
1887 required: false,
1888 children: None,
1889 constraint: None,
1890 },
1891 FieldSchema {
1892 label: "FieldOfView",
1893 expected_type: GffType::Single,
1894 required: false,
1895 children: None,
1896 constraint: None,
1897 },
1898 FieldSchema {
1899 label: "MicRange",
1900 expected_type: GffType::Single,
1901 required: false,
1902 children: None,
1903 constraint: None,
1904 },
1905];
1906
1907impl GffSchema for Git {
1908 fn schema() -> &'static [FieldSchema] {
1909 static SCHEMA: &[FieldSchema] = &[
1910 FieldSchema {
1912 label: "CurrentWeather",
1913 expected_type: GffType::UInt8,
1914 required: false,
1915 children: None,
1916 constraint: None,
1917 },
1918 FieldSchema {
1919 label: "WeatherStarted",
1920 expected_type: GffType::UInt8,
1921 required: false,
1922 children: None,
1923 constraint: None,
1924 },
1925 FieldSchema {
1926 label: "UseTemplates",
1927 expected_type: GffType::UInt8,
1928 required: false,
1929 children: None,
1930 constraint: None,
1931 },
1932 FieldSchema {
1934 label: "Creature List",
1935 expected_type: GffType::List,
1936 required: false,
1937 children: Some(CREATURE_LIST_CHILDREN),
1938 constraint: None,
1939 },
1940 FieldSchema {
1941 label: "List",
1942 expected_type: GffType::List,
1943 required: false,
1944 children: Some(ITEM_LIST_CHILDREN),
1945 constraint: None,
1946 },
1947 FieldSchema {
1948 label: "Door List",
1949 expected_type: GffType::List,
1950 required: false,
1951 children: Some(DOOR_LIST_CHILDREN),
1952 constraint: None,
1953 },
1954 FieldSchema {
1955 label: "Placeable List",
1956 expected_type: GffType::List,
1957 required: false,
1958 children: Some(PLACEABLE_LIST_CHILDREN),
1959 constraint: None,
1960 },
1961 FieldSchema {
1962 label: "WaypointList",
1963 expected_type: GffType::List,
1964 required: false,
1965 children: Some(WAYPOINT_LIST_CHILDREN),
1966 constraint: None,
1967 },
1968 FieldSchema {
1969 label: "SoundList",
1970 expected_type: GffType::List,
1971 required: false,
1972 children: Some(SOUND_LIST_CHILDREN),
1973 constraint: None,
1974 },
1975 FieldSchema {
1976 label: "TriggerList",
1977 expected_type: GffType::List,
1978 required: false,
1979 children: Some(TRIGGER_LIST_CHILDREN),
1980 constraint: None,
1981 },
1982 FieldSchema {
1983 label: "StoreList",
1984 expected_type: GffType::List,
1985 required: false,
1986 children: Some(STORE_LIST_CHILDREN),
1987 constraint: None,
1988 },
1989 FieldSchema {
1990 label: "Encounter List",
1991 expected_type: GffType::List,
1992 required: false,
1993 children: Some(ENCOUNTER_LIST_CHILDREN),
1994 constraint: None,
1995 },
1996 FieldSchema {
1997 label: "AreaEffectList",
1998 expected_type: GffType::List,
1999 required: false,
2000 children: Some(AREA_EFFECT_LIST_CHILDREN),
2001 constraint: None,
2002 },
2003 FieldSchema {
2005 label: "AreaProperties",
2006 expected_type: GffType::Struct,
2007 required: false,
2008 children: Some(AREA_PROPERTIES_CHILDREN),
2009 constraint: None,
2010 },
2011 FieldSchema {
2012 label: "AreaMap",
2013 expected_type: GffType::Struct,
2014 required: false,
2015 children: Some(AREA_MAP_CHILDREN),
2016 constraint: None,
2017 },
2018 FieldSchema {
2020 label: "CameraList",
2021 expected_type: GffType::List,
2022 required: false,
2023 children: Some(CAMERA_LIST_CHILDREN),
2024 constraint: None,
2025 },
2026 FieldSchema {
2028 label: "VarTable",
2029 expected_type: GffType::List,
2030 required: false,
2031 children: None,
2032 constraint: None,
2033 },
2034 ];
2035 SCHEMA
2036 }
2037}
2038
2039#[cfg(test)]
2040mod tests {
2041 use super::*;
2042
2043 fn make_test_git_gff() -> Gff {
2045 let mut root = GffStruct::new(-1);
2046 root.push_field("CurrentWeather", GffValue::UInt8(0));
2047 root.push_field("WeatherStarted", GffValue::UInt8(0));
2048 root.push_field("UseTemplates", GffValue::UInt8(1));
2049
2050 let mut creature = GffStruct::new(4);
2052 creature.push_field("TemplateResRef", GffValue::resref_lit("n_darthbandon"));
2053 creature.push_field("XPosition", GffValue::Single(10.0));
2054 creature.push_field("YPosition", GffValue::Single(20.0));
2055 creature.push_field("ZPosition", GffValue::Single(0.5));
2056 creature.push_field("XOrientation", GffValue::Single(0.0));
2057 creature.push_field("YOrientation", GffValue::Single(1.0));
2058 creature.push_field("ZOrientation", GffValue::Single(0.0));
2059 creature.push_field("ObjectId", GffValue::UInt32(0));
2060 root.push_field("Creature List", GffValue::List(vec![creature]));
2061
2062 let mut placeable = GffStruct::new(9);
2064 placeable.push_field("TemplateResRef", GffValue::resref_lit("plc_footlkr"));
2065 placeable.push_field("Bearing", GffValue::Single(1.57));
2066 placeable.push_field("X", GffValue::Single(15.0));
2067 placeable.push_field("Y", GffValue::Single(25.0));
2068 placeable.push_field("Z", GffValue::Single(0.0));
2069 placeable.push_field("ObjectId", GffValue::UInt32(0));
2070 root.push_field("Placeable List", GffValue::List(vec![placeable]));
2071
2072 let mut trigger = GffStruct::new(1);
2074 trigger.push_field("TemplateResRef", GffValue::resref_lit("newtransition9"));
2075 trigger.push_field("LinkedToModule", GffValue::resref_lit("tar_m02aa"));
2076 trigger.push_field(
2077 "TransitionDestin",
2078 GffValue::LocalizedString(GffLocalizedString::new(StrRef::from_raw(12345))),
2079 );
2080 trigger.push_field("LinkedTo", GffValue::String("wp_target".into()));
2081 trigger.push_field("LinkedToFlags", GffValue::UInt8(2));
2082 trigger.push_field("XPosition", GffValue::Single(5.0));
2083 trigger.push_field("YPosition", GffValue::Single(5.0));
2084 trigger.push_field("ZPosition", GffValue::Single(0.0));
2085 let mut pt0 = GffStruct::new(0);
2086 pt0.push_field("PointX", GffValue::Single(0.0));
2087 pt0.push_field("PointY", GffValue::Single(0.0));
2088 pt0.push_field("PointZ", GffValue::Single(0.0));
2089 let mut pt1 = GffStruct::new(0);
2090 pt1.push_field("PointX", GffValue::Single(5.0));
2091 pt1.push_field("PointY", GffValue::Single(0.0));
2092 pt1.push_field("PointZ", GffValue::Single(0.0));
2093 let mut pt2 = GffStruct::new(0);
2094 pt2.push_field("PointX", GffValue::Single(5.0));
2095 pt2.push_field("PointY", GffValue::Single(5.0));
2096 pt2.push_field("PointZ", GffValue::Single(0.0));
2097 trigger.push_field("Geometry", GffValue::List(vec![pt0, pt1, pt2]));
2098 trigger.push_field("ObjectId", GffValue::UInt32(0));
2099 root.push_field("TriggerList", GffValue::List(vec![trigger]));
2100
2101 let mut props = GffStruct::new(0);
2103 props.push_field("Unescapable", GffValue::UInt8(0));
2104 props.push_field("StealthXPMax", GffValue::UInt32(100));
2105 props.push_field("StealthXPCurrent", GffValue::UInt32(0));
2106 props.push_field("StealthXPLoss", GffValue::UInt32(10));
2107 props.push_field("StealthXPEnabled", GffValue::UInt8(1));
2108 props.push_field("TransPending", GffValue::UInt8(0));
2109 props.push_field("TransPendNextID", GffValue::UInt8(0));
2110 props.push_field("TransPendCurrID", GffValue::UInt8(0));
2111 props.push_field("SunFogColor", GffValue::UInt32(0x00ABCDEF));
2112 props.push_field("MusicDelay", GffValue::Int32(6000));
2113 props.push_field("MusicDay", GffValue::Int32(48));
2114 props.push_field("MusicNight", GffValue::Int32(49));
2115 props.push_field("MusicBattle", GffValue::Int32(25));
2116 props.push_field("AmbientSndDay", GffValue::Int32(12));
2117 props.push_field("AmbientSndNight", GffValue::Int32(13));
2118 props.push_field("AmbientSndDayVol", GffValue::Int32(50));
2119 props.push_field("AmbientSndNitVol", GffValue::Int32(40));
2120 root.push_field("AreaProperties", GffValue::Struct(Box::new(props)));
2121
2122 root.push_field("List", GffValue::List(vec![]));
2124 root.push_field("Door List", GffValue::List(vec![]));
2125 root.push_field("WaypointList", GffValue::List(vec![]));
2126 root.push_field("SoundList", GffValue::List(vec![]));
2127 root.push_field("StoreList", GffValue::List(vec![]));
2128 root.push_field("Encounter List", GffValue::List(vec![]));
2129 root.push_field("AreaEffectList", GffValue::List(vec![]));
2130 root.push_field("CameraList", GffValue::List(vec![]));
2131
2132 Gff::new(*b"GIT ", root)
2133 }
2134
2135 #[test]
2136 fn reads_root_scalars() {
2137 let gff = make_test_git_gff();
2138 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2139
2140 assert_eq!(git.current_weather, 0);
2141 assert!(!git.weather_started);
2142 assert!(git.use_templates);
2143 }
2144
2145 #[test]
2146 fn reads_creature_list() {
2147 let gff = make_test_git_gff();
2148 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2149
2150 assert_eq!(git.creatures.len(), 1);
2151 let c = &git.creatures[0];
2152 assert_eq!(c.template_resref, "n_darthbandon");
2153 assert!((c.x_position - 10.0).abs() < f32::EPSILON);
2154 assert!((c.y_position - 20.0).abs() < f32::EPSILON);
2155 assert!((c.y_orientation - 1.0).abs() < f32::EPSILON);
2156 }
2157
2158 #[test]
2159 fn reads_placeable_list() {
2160 let gff = make_test_git_gff();
2161 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2162
2163 assert_eq!(git.placeables.len(), 1);
2164 let p = &git.placeables[0];
2165 assert_eq!(p.template_resref, "plc_footlkr");
2166 assert!((p.bearing - 1.57).abs() < 0.01);
2167 assert!((p.x - 15.0).abs() < f32::EPSILON);
2168 }
2169
2170 #[test]
2171 fn reads_trigger_with_geometry() {
2172 let gff = make_test_git_gff();
2173 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2174
2175 assert_eq!(git.triggers.len(), 1);
2176 let t = &git.triggers[0];
2177 assert_eq!(t.template_resref, "newtransition9");
2178 assert_eq!(t.linked_to_module, "tar_m02aa");
2179 assert_eq!(t.linked_to, "wp_target");
2180 assert_eq!(t.linked_to_flags, 2);
2181 assert_eq!(t.geometry.len(), 3);
2182 assert!((t.geometry[1].point_x - 5.0).abs() < f32::EPSILON);
2183 }
2184
2185 #[test]
2186 fn reads_area_properties() {
2187 let gff = make_test_git_gff();
2188 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2189
2190 let ap = git
2191 .area_properties
2192 .as_ref()
2193 .expect("test GFF includes AreaProperties");
2194 assert!(!ap.unescapable);
2195 assert_eq!(ap.stealth_xp_max, 100);
2196 assert!(ap.stealth_xp_enabled);
2197 assert_eq!(ap.music_day, 48);
2198 assert_eq!(ap.music_night, 49);
2199 assert_eq!(ap.music_battle, 25);
2200 assert_eq!(ap.ambient_snd_day_vol, 50);
2201 assert_eq!(ap.ambient_snd_nit_vol, 40);
2202 }
2203
2204 #[test]
2205 fn all_fields_survive_synthetic_roundtrip() {
2206 let gff = make_test_git_gff();
2207 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2208 let bytes = write_git_to_vec(&git).expect("write succeeds");
2209 let reparsed = read_git_from_bytes(&bytes).expect("reparse succeeds");
2210
2211 assert_eq!(reparsed, git);
2212 }
2213
2214 #[test]
2215 fn typed_edits_roundtrip_through_gff_writer() {
2216 let gff = make_test_git_gff();
2217 let mut git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2218 git.creatures[0].template_resref = ResRef::new("n_sithsoldier").expect("valid test resref");
2219 git.creatures[0].x_position = 100.0;
2220 git.area_properties
2221 .as_mut()
2222 .expect("test GFF includes AreaProperties")
2223 .music_day = 99;
2224
2225 let bytes = write_git_to_vec(&git).expect("write succeeds");
2226 let reparsed = read_git_from_bytes(&bytes).expect("reparse succeeds");
2227
2228 assert_eq!(reparsed.creatures[0].template_resref, "n_sithsoldier");
2229 assert!((reparsed.creatures[0].x_position - 100.0).abs() < f32::EPSILON);
2230 assert_eq!(
2231 reparsed
2232 .area_properties
2233 .as_ref()
2234 .expect("roundtripped GIT must have AreaProperties")
2235 .music_day,
2236 99
2237 );
2238 }
2239
2240 #[test]
2241 fn read_git_from_reader_matches_bytes_path() {
2242 let gff = make_test_git_gff();
2243 let bytes = {
2244 let mut c = Cursor::new(Vec::new());
2245 write_gff(&mut c, &gff).expect("GFF write to cursor always succeeds");
2246 c.into_inner()
2247 };
2248
2249 let mut cursor = Cursor::new(&bytes);
2250 let via_reader = read_git(&mut cursor).expect("reader parse succeeds");
2251 let via_bytes = read_git_from_bytes(&bytes).expect("bytes parse succeeds");
2252
2253 assert_eq!(via_reader, via_bytes);
2254 }
2255
2256 #[test]
2257 fn rejects_non_git_file_type() {
2258 let mut gff = make_test_git_gff();
2259 gff.file_type = *b"UTW ";
2260
2261 let err = Git::from_gff(&gff).expect_err("UTW must be rejected as GIT input");
2262 assert!(matches!(
2263 err,
2264 GitError::UnsupportedFileType(ft) if ft == *b"UTW "
2265 ));
2266 }
2267
2268 #[test]
2269 fn write_git_matches_direct_gff_writer() {
2270 let gff = make_test_git_gff();
2271 let git = Git::from_gff(&gff).expect("valid GIT GFF must parse");
2272
2273 let via_typed = write_git_to_vec(&git).expect("typed write succeeds");
2274
2275 let mut direct = Cursor::new(Vec::new());
2276 write_gff(&mut direct, &git.to_gff()).expect("direct write succeeds");
2277
2278 assert_eq!(via_typed, direct.into_inner());
2279 }
2280
2281 const TEST_GIT: &[u8] = include_bytes!(concat!(
2284 env!("CARGO_MANIFEST_DIR"),
2285 "/../../fixtures/test.git"
2286 ));
2287 const K1_GIT: &[u8] = include_bytes!(concat!(
2288 env!("CARGO_MANIFEST_DIR"),
2289 "/../../fixtures/k1_same_git_test.git"
2290 ));
2291
2292 #[test]
2293 fn parses_test_git_fixture() {
2294 let git = read_git_from_bytes(TEST_GIT).expect("test.git must parse");
2295 let total = git.creatures.len()
2297 + git.items.len()
2298 + git.doors.len()
2299 + git.placeables.len()
2300 + git.waypoints.len()
2301 + git.sounds.len()
2302 + git.triggers.len()
2303 + git.stores.len()
2304 + git.encounters.len()
2305 + git.area_effects.len();
2306 assert!(total > 0, "fixture should have some instance data");
2307 }
2308
2309 #[test]
2310 fn parses_k1_git_fixture() {
2311 let git = read_git_from_bytes(K1_GIT).expect("k1 GIT must parse");
2312 let total = git.creatures.len()
2313 + git.items.len()
2314 + git.doors.len()
2315 + git.placeables.len()
2316 + git.waypoints.len()
2317 + git.sounds.len()
2318 + git.triggers.len()
2319 + git.stores.len()
2320 + git.encounters.len()
2321 + git.area_effects.len();
2322 assert!(total > 0, "K1 fixture should have some instance data");
2323 }
2324
2325 #[test]
2326 fn all_fields_survive_typed_roundtrip() {
2327 let git = read_git_from_bytes(TEST_GIT).expect("test.git must parse");
2328 let bytes = write_git_to_vec(&git).expect("write succeeds");
2329 let reparsed = read_git_from_bytes(&bytes).expect("reparse succeeds");
2330
2331 assert_eq!(reparsed, git);
2332 }
2333
2334 #[test]
2335 fn all_fields_survive_typed_roundtrip_k1() {
2336 let git = read_git_from_bytes(K1_GIT).expect("k1 GIT must parse");
2337 let bytes = write_git_to_vec(&git).expect("write succeeds");
2338 let reparsed = read_git_from_bytes(&bytes).expect("reparse succeeds");
2339
2340 assert_eq!(reparsed, git);
2341 }
2342
2343 #[test]
2346 fn schema_field_count() {
2347 assert_eq!(Git::schema().len(), 17);
2348 }
2349
2350 #[test]
2351 fn schema_no_duplicate_labels() {
2352 let schema = Git::schema();
2353 let mut labels: Vec<&str> = schema.iter().map(|f| f.label).collect();
2354 labels.sort();
2355 let before = labels.len();
2356 labels.dedup();
2357 assert_eq!(before, labels.len(), "duplicate labels in GIT schema");
2358 }
2359
2360 #[test]
2361 fn schema_sub_schema_field_counts() {
2362 let schema = Git::schema();
2363
2364 let creature = schema
2365 .iter()
2366 .find(|f| f.label == "Creature List")
2367 .expect("schema must contain Creature List");
2368 assert_eq!(
2369 creature.children.expect("Creature List has children").len(),
2370 8
2371 );
2372
2373 let item = schema
2374 .iter()
2375 .find(|f| f.label == "List")
2376 .expect("schema must contain List");
2377 assert_eq!(item.children.expect("List has children").len(), 8);
2378
2379 let door = schema
2380 .iter()
2381 .find(|f| f.label == "Door List")
2382 .expect("schema must contain Door List");
2383 assert_eq!(door.children.expect("Door List has children").len(), 5);
2384
2385 let placeable = schema
2386 .iter()
2387 .find(|f| f.label == "Placeable List")
2388 .expect("schema must contain Placeable List");
2389 assert_eq!(
2390 placeable
2391 .children
2392 .expect("Placeable List has children")
2393 .len(),
2394 6
2395 );
2396
2397 let waypoint = schema
2398 .iter()
2399 .find(|f| f.label == "WaypointList")
2400 .expect("schema must contain WaypointList");
2401 assert_eq!(
2402 waypoint.children.expect("WaypointList has children").len(),
2403 4
2404 );
2405
2406 let sound = schema
2407 .iter()
2408 .find(|f| f.label == "SoundList")
2409 .expect("schema must contain SoundList");
2410 assert_eq!(sound.children.expect("SoundList has children").len(), 6);
2411
2412 let trigger = schema
2413 .iter()
2414 .find(|f| f.label == "TriggerList")
2415 .expect("schema must contain TriggerList");
2416 assert_eq!(
2417 trigger.children.expect("TriggerList has children").len(),
2418 10
2419 );
2420
2421 let store = schema
2422 .iter()
2423 .find(|f| f.label == "StoreList")
2424 .expect("schema must contain StoreList");
2425 assert_eq!(store.children.expect("StoreList has children").len(), 8);
2426
2427 let encounter = schema
2428 .iter()
2429 .find(|f| f.label == "Encounter List")
2430 .expect("schema must contain Encounter List");
2431 assert_eq!(
2432 encounter
2433 .children
2434 .expect("Encounter List has children")
2435 .len(),
2436 7
2437 );
2438
2439 let area_effect = schema
2440 .iter()
2441 .find(|f| f.label == "AreaEffectList")
2442 .expect("schema must contain AreaEffectList");
2443 assert_eq!(
2444 area_effect
2445 .children
2446 .expect("AreaEffectList has children")
2447 .len(),
2448 7
2449 );
2450
2451 let area_props = schema
2452 .iter()
2453 .find(|f| f.label == "AreaProperties")
2454 .expect("schema must contain AreaProperties");
2455 assert_eq!(
2456 area_props
2457 .children
2458 .expect("AreaProperties has children")
2459 .len(),
2460 17
2461 );
2462
2463 let area_map = schema
2464 .iter()
2465 .find(|f| f.label == "AreaMap")
2466 .expect("schema must contain AreaMap");
2467 assert_eq!(area_map.children.expect("AreaMap has children").len(), 4);
2468
2469 let camera = schema
2470 .iter()
2471 .find(|f| f.label == "CameraList")
2472 .expect("schema must contain CameraList");
2473 assert_eq!(camera.children.expect("CameraList has children").len(), 7);
2474 }
2475
2476 #[test]
2477 fn schema_trigger_geometry_has_children() {
2478 let schema = Git::schema();
2479 let trigger = schema
2480 .iter()
2481 .find(|f| f.label == "TriggerList")
2482 .expect("schema must contain TriggerList");
2483 let trigger_children = trigger.children.expect("TriggerList has children");
2484 let geometry = trigger_children
2485 .iter()
2486 .find(|f| f.label == "Geometry")
2487 .expect("TriggerList must contain Geometry");
2488 assert_eq!(geometry.children.expect("Geometry has children").len(), 3);
2489 }
2490
2491 #[test]
2492 fn schema_encounter_nested_lists_have_children() {
2493 let schema = Git::schema();
2494 let encounter = schema
2495 .iter()
2496 .find(|f| f.label == "Encounter List")
2497 .expect("schema must contain Encounter List");
2498 let encounter_children = encounter.children.expect("Encounter List has children");
2499
2500 let geometry = encounter_children
2501 .iter()
2502 .find(|f| f.label == "Geometry")
2503 .expect("Encounter List must contain Geometry");
2504 assert_eq!(geometry.children.expect("Geometry has children").len(), 3);
2505
2506 let spawn_points = encounter_children
2507 .iter()
2508 .find(|f| f.label == "SpawnPointList")
2509 .expect("Encounter List must contain SpawnPointList");
2510 assert_eq!(
2511 spawn_points
2512 .children
2513 .expect("SpawnPointList has children")
2514 .len(),
2515 4
2516 );
2517 }
2518
2519 #[test]
2520 fn schema_var_table_has_no_children() {
2521 let schema = Git::schema();
2522 let var_table = schema
2523 .iter()
2524 .find(|f| f.label == "VarTable")
2525 .expect("schema must contain VarTable");
2526 assert!(var_table.children.is_none());
2527 }
2528}