1use std::io::{Cursor, Read, Write};
55
56use crate::gff_helpers::{
57 get_bool, get_f32, get_i32, get_locstring, get_resref, get_string, get_u16, get_u32, get_u8,
58 upsert_field,
59};
60use rakata_core::{ResRef, StrRef};
61use rakata_formats::{
62 gff_schema::{AbsentDefault, FieldLife, FieldSchema, GffSchema, GffType},
63 read_gff, read_gff_from_bytes, write_gff, Gff, GffBinaryError, GffLocalizedString, GffStruct,
64 GffValue,
65};
66use thiserror::Error;
67
68#[derive(Debug, Clone, PartialEq, Default)]
70pub struct DlgLink {
71 pub active: ResRef,
73 pub index: u32,
75 pub display_inactive: bool,
84}
85
86impl DlgLink {
87 fn from_gff_struct(s: &GffStruct) -> Self {
88 Self {
89 active: get_resref(s, "Active").unwrap_or_default(),
90 index: get_u32(s, "Index").unwrap_or(0),
91 display_inactive: get_bool(s, "DisplayInactive").unwrap_or(false),
92 }
93 }
94
95 fn to_gff_struct(&self, include_display_inactive: bool) -> GffStruct {
96 let mut s = GffStruct::new(0);
97 upsert_field(&mut s, "Active", GffValue::ResRef(self.active));
98 upsert_field(&mut s, "Index", GffValue::UInt32(self.index));
99 if include_display_inactive && self.display_inactive {
110 upsert_field(
111 &mut s,
112 "DisplayInactive",
113 GffValue::UInt8(u8::from(self.display_inactive)),
114 );
115 }
116 s
117 }
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub struct DlgAnimation {
123 pub participant: String,
125 pub animation: u16,
127}
128
129impl DlgAnimation {
130 fn from_gff_struct(s: &GffStruct) -> Self {
131 Self {
132 participant: get_string(s, "Participant").unwrap_or_default(),
133 animation: get_u16(s, "Animation").unwrap_or(0),
134 }
135 }
136
137 fn to_gff_struct(&self) -> GffStruct {
138 let mut s = GffStruct::new(0);
139 upsert_field(
140 &mut s,
141 "Participant",
142 GffValue::String(self.participant.clone()),
143 );
144 upsert_field(&mut s, "Animation", GffValue::UInt16(self.animation));
145 s
146 }
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub struct DlgStunt {
152 pub participant: String,
154 pub stunt_model: ResRef,
156}
157
158impl DlgStunt {
159 fn from_gff_struct(s: &GffStruct) -> Self {
160 Self {
161 participant: get_string(s, "Participant").unwrap_or_default(),
162 stunt_model: get_resref(s, "StuntModel").unwrap_or_default(),
163 }
164 }
165
166 fn to_gff_struct(&self) -> GffStruct {
167 let mut s = GffStruct::new(0);
168 upsert_field(
169 &mut s,
170 "Participant",
171 GffValue::String(self.participant.clone()),
172 );
173 upsert_field(&mut s, "StuntModel", GffValue::ResRef(self.stunt_model));
174 s
175 }
176}
177
178#[derive(Debug, Clone, PartialEq)]
185pub struct DlgNode {
186 pub text: GffLocalizedString,
189 pub script: ResRef,
191 pub speaker: String,
193 pub wait_flags: u32,
195 pub quest: String,
197 pub quest_entry: u32,
199 pub plot_index: i32,
201 pub plot_xp_percentage: f32,
203 pub delay: u32,
205 pub fade_type: u8,
207 pub fade_color: [f32; 3],
209 pub fade_delay: f32,
211 pub fade_length: f32,
213 pub sound: ResRef,
215 pub vo_resref: ResRef,
217 pub sound_exists: bool,
219 pub animations: Vec<DlgAnimation>,
221
222 pub listener: String,
225 pub camera_angle: u32,
227 pub camera_id: i32,
229 pub cam_height_offset: f32,
231 pub tar_height_offset: f32,
233 pub camera_animation: u16,
235 pub cam_vid_effect: i32,
237 pub cam_field_of_view: f32,
239
240 pub links: Vec<DlgLink>,
244}
245
246impl Default for DlgNode {
247 fn default() -> Self {
248 Self {
249 text: GffLocalizedString::new(StrRef::invalid()),
250 script: ResRef::blank(),
251 speaker: String::new(),
252 wait_flags: 0,
253 quest: String::new(),
254 quest_entry: 0,
255 plot_index: -1,
256 plot_xp_percentage: 1.0,
257 delay: u32::MAX,
258 fade_type: 0,
259 fade_color: [0.0, 0.0, 0.0],
260 fade_delay: 0.0,
261 fade_length: 0.0,
262 sound: ResRef::blank(),
263 vo_resref: ResRef::blank(),
264 sound_exists: false,
265 animations: Vec::new(),
266 listener: String::new(),
267 camera_angle: 0,
268 camera_id: -1,
269 cam_height_offset: 0.0,
270 tar_height_offset: 0.0,
271 camera_animation: 0,
272 cam_vid_effect: -1,
273 cam_field_of_view: -1.0,
274 links: Vec::new(),
275 }
276 }
277}
278
279impl DlgNode {
280 fn from_gff_struct(s: &GffStruct, link_field: &str) -> Self {
281 let animations = match s.field("AnimList") {
282 Some(GffValue::List(elements)) => {
283 elements.iter().map(DlgAnimation::from_gff_struct).collect()
284 }
285 _ => Vec::new(),
286 };
287
288 let links = match s.field(link_field) {
289 Some(GffValue::List(elements)) => {
290 elements.iter().map(DlgLink::from_gff_struct).collect()
291 }
292 _ => Vec::new(),
293 };
294
295 let fade_color = match s.field("FadeColor") {
296 Some(GffValue::Vector3(v)) => *v,
297 _ => [0.0, 0.0, 0.0],
298 };
299
300 Self {
301 text: get_locstring(s, "Text")
302 .cloned()
303 .unwrap_or_else(|| GffLocalizedString::new(StrRef::invalid())),
304 script: get_resref(s, "Script").unwrap_or_default(),
305 speaker: get_string(s, "Speaker").unwrap_or_default(),
306 wait_flags: get_u32(s, "WaitFlags").unwrap_or(0),
307 quest: get_string(s, "Quest").unwrap_or_default(),
308 quest_entry: get_u32(s, "QuestEntry").unwrap_or(0),
309 plot_index: get_i32(s, "PlotIndex").unwrap_or(-1),
310 plot_xp_percentage: get_f32(s, "PlotXPPercentage").unwrap_or(1.0),
311 delay: get_u32(s, "Delay").unwrap_or(u32::MAX),
312 fade_type: get_u8(s, "FadeType").unwrap_or(0),
313 fade_color,
314 fade_delay: get_f32(s, "FadeDelay").unwrap_or(0.0),
315 fade_length: get_f32(s, "FadeLength").unwrap_or(0.0),
316 sound: get_resref(s, "Sound").unwrap_or_default(),
317 vo_resref: get_resref(s, "VO_ResRef").unwrap_or_default(),
318 sound_exists: get_bool(s, "SoundExists").unwrap_or(false),
319 animations,
320 listener: get_string(s, "Listener").unwrap_or_default(),
321 camera_angle: get_u32(s, "CameraAngle").unwrap_or(0),
322 camera_id: get_i32(s, "CameraID").unwrap_or(-1),
323 cam_height_offset: get_f32(s, "CamHeightOffset").unwrap_or(0.0),
324 tar_height_offset: get_f32(s, "TarHeightOffset").unwrap_or(0.0),
325 camera_animation: get_u16(s, "CameraAnimation").unwrap_or(0),
326 cam_vid_effect: get_i32(s, "CamVidEffect").unwrap_or(-1),
327 cam_field_of_view: get_f32(s, "CamFieldOfView").unwrap_or(-1.0),
328 links,
329 }
330 }
331
332 fn to_gff_struct(
333 &self,
334 link_field: &str,
335 include_display_inactive: bool,
336 struct_id: i32,
337 ) -> GffStruct {
338 let mut s = GffStruct::new(struct_id);
339
340 upsert_field(&mut s, "Text", GffValue::LocalizedString(self.text.clone()));
341 upsert_field(&mut s, "Script", GffValue::ResRef(self.script));
342 upsert_field(&mut s, "Speaker", GffValue::String(self.speaker.clone()));
343 upsert_field(&mut s, "WaitFlags", GffValue::UInt32(self.wait_flags));
344 upsert_field(&mut s, "Quest", GffValue::String(self.quest.clone()));
345 upsert_field(&mut s, "QuestEntry", GffValue::UInt32(self.quest_entry));
346 upsert_field(&mut s, "PlotIndex", GffValue::Int32(self.plot_index));
347 upsert_field(
348 &mut s,
349 "PlotXPPercentage",
350 GffValue::Single(self.plot_xp_percentage),
351 );
352 upsert_field(&mut s, "Delay", GffValue::UInt32(self.delay));
353 upsert_field(&mut s, "FadeType", GffValue::UInt8(self.fade_type));
354 upsert_field(&mut s, "FadeColor", GffValue::Vector3(self.fade_color));
355 upsert_field(&mut s, "FadeDelay", GffValue::Single(self.fade_delay));
356 upsert_field(&mut s, "FadeLength", GffValue::Single(self.fade_length));
357 upsert_field(&mut s, "Sound", GffValue::ResRef(self.sound));
358 upsert_field(&mut s, "VO_ResRef", GffValue::ResRef(self.vo_resref));
359 upsert_field(
360 &mut s,
361 "SoundExists",
362 GffValue::UInt8(u8::from(self.sound_exists)),
363 );
364
365 let anim_structs: Vec<GffStruct> =
366 self.animations.iter().map(|a| a.to_gff_struct()).collect();
367 upsert_field(&mut s, "AnimList", GffValue::List(anim_structs));
368
369 upsert_field(&mut s, "Listener", GffValue::String(self.listener.clone()));
370 upsert_field(&mut s, "CameraAngle", GffValue::UInt32(self.camera_angle));
371 upsert_field(&mut s, "CameraID", GffValue::Int32(self.camera_id));
372 upsert_field(
373 &mut s,
374 "CamHeightOffset",
375 GffValue::Single(self.cam_height_offset),
376 );
377 upsert_field(
378 &mut s,
379 "TarHeightOffset",
380 GffValue::Single(self.tar_height_offset),
381 );
382 upsert_field(
383 &mut s,
384 "CameraAnimation",
385 GffValue::UInt16(self.camera_animation),
386 );
387 upsert_field(&mut s, "CamVidEffect", GffValue::Int32(self.cam_vid_effect));
388 upsert_field(
389 &mut s,
390 "CamFieldOfView",
391 GffValue::Single(self.cam_field_of_view),
392 );
393
394 let link_structs: Vec<GffStruct> = self
395 .links
396 .iter()
397 .map(|l| l.to_gff_struct(include_display_inactive))
398 .collect();
399 upsert_field(&mut s, link_field, GffValue::List(link_structs));
400
401 s
402 }
403}
404
405#[derive(Debug, Clone, PartialEq)]
407pub struct Dlg {
408 pub camera_model: ResRef,
411 pub delay_entry: u32,
413 pub delay_reply: u32,
415 pub end_conversation: ResRef,
417 pub end_conver_abort: ResRef,
419 pub skippable: bool,
421 pub conversation_type: i32,
423 pub computer_type: u8,
425 pub ambient_track: ResRef,
427 pub unequip_items: bool,
429 pub unequip_h_item: bool,
431 pub animated_cut: bool,
433 pub old_hit_check: bool,
435
436 pub starting_list: Vec<DlgLink>,
439 pub entries: Vec<DlgNode>,
441 pub replies: Vec<DlgNode>,
443 pub stunt_list: Vec<DlgStunt>,
445}
446
447impl Default for Dlg {
448 fn default() -> Self {
449 Self {
450 camera_model: ResRef::blank(),
451 delay_entry: 0,
452 delay_reply: 0,
453 end_conversation: ResRef::blank(),
454 end_conver_abort: ResRef::blank(),
455 skippable: true,
456 conversation_type: 0,
457 computer_type: 0,
458 ambient_track: ResRef::blank(),
459 unequip_items: false,
460 unequip_h_item: false,
461 animated_cut: false,
462 old_hit_check: false,
463 starting_list: Vec::new(),
464 entries: Vec::new(),
465 replies: Vec::new(),
466 stunt_list: Vec::new(),
467 }
468 }
469}
470
471impl Dlg {
472 pub fn new() -> Self {
474 Self::default()
475 }
476
477 pub fn from_gff(gff: &Gff) -> Result<Self, DlgError> {
479 if gff.file_type != *b"DLG " && gff.file_type != *b"GFF " {
480 return Err(DlgError::UnsupportedFileType(gff.file_type));
481 }
482
483 let root = &gff.root;
484
485 let starting_list = match root.field("StartingList") {
486 Some(GffValue::List(elements)) => {
487 elements.iter().map(DlgLink::from_gff_struct).collect()
488 }
489 _ => Vec::new(),
490 };
491
492 let entries = match root.field("EntryList") {
493 Some(GffValue::List(elements)) => elements
494 .iter()
495 .map(|s| DlgNode::from_gff_struct(s, "RepliesList"))
496 .collect(),
497 _ => Vec::new(),
498 };
499
500 let replies = match root.field("ReplyList") {
501 Some(GffValue::List(elements)) => elements
502 .iter()
503 .map(|s| DlgNode::from_gff_struct(s, "EntriesList"))
504 .collect(),
505 _ => Vec::new(),
506 };
507
508 let stunt_list = match root.field("StuntList") {
509 Some(GffValue::List(elements)) => {
510 elements.iter().map(DlgStunt::from_gff_struct).collect()
511 }
512 _ => Vec::new(),
513 };
514
515 Ok(Self {
516 camera_model: get_resref(root, "CameraModel").unwrap_or_default(),
517 delay_entry: get_u32(root, "DelayEntry").unwrap_or(0),
518 delay_reply: get_u32(root, "DelayReply").unwrap_or(0),
519 end_conversation: get_resref(root, "EndConversation").unwrap_or_default(),
520 end_conver_abort: get_resref(root, "EndConverAbort").unwrap_or_default(),
521 skippable: get_bool(root, "Skippable").unwrap_or(true),
522 conversation_type: get_i32(root, "ConversationType").unwrap_or(0),
523 computer_type: get_u8(root, "ComputerType").unwrap_or(0),
524 ambient_track: get_resref(root, "AmbientTrack").unwrap_or_default(),
525 unequip_items: get_bool(root, "UnequipItems").unwrap_or(false),
526 unequip_h_item: get_bool(root, "UnequipHItem").unwrap_or(false),
527 animated_cut: get_bool(root, "AnimatedCut").unwrap_or(false),
528 old_hit_check: get_bool(root, "OldHitCheck").unwrap_or(false),
529 starting_list,
530 entries,
531 replies,
532 stunt_list,
533 })
534 }
535
536 pub fn to_gff(&self) -> Gff {
540 let mut root = GffStruct::new(-1);
541
542 upsert_field(
543 &mut root,
544 "CameraModel",
545 GffValue::ResRef(self.camera_model),
546 );
547 upsert_field(&mut root, "DelayEntry", GffValue::UInt32(self.delay_entry));
548 upsert_field(&mut root, "DelayReply", GffValue::UInt32(self.delay_reply));
549 upsert_field(
550 &mut root,
551 "EndConversation",
552 GffValue::ResRef(self.end_conversation),
553 );
554 upsert_field(
555 &mut root,
556 "EndConverAbort",
557 GffValue::ResRef(self.end_conver_abort),
558 );
559 upsert_field(
560 &mut root,
561 "Skippable",
562 GffValue::UInt8(u8::from(self.skippable)),
563 );
564 upsert_field(
565 &mut root,
566 "ConversationType",
567 GffValue::Int32(self.conversation_type),
568 );
569 upsert_field(
570 &mut root,
571 "ComputerType",
572 GffValue::UInt8(self.computer_type),
573 );
574 upsert_field(
575 &mut root,
576 "AmbientTrack",
577 GffValue::ResRef(self.ambient_track),
578 );
579 upsert_field(
580 &mut root,
581 "UnequipItems",
582 GffValue::UInt8(u8::from(self.unequip_items)),
583 );
584 upsert_field(
585 &mut root,
586 "UnequipHItem",
587 GffValue::UInt8(u8::from(self.unequip_h_item)),
588 );
589 upsert_field(
590 &mut root,
591 "AnimatedCut",
592 GffValue::UInt8(u8::from(self.animated_cut)),
593 );
594 upsert_field(
595 &mut root,
596 "OldHitCheck",
597 GffValue::UInt8(u8::from(self.old_hit_check)),
598 );
599
600 let start_structs: Vec<GffStruct> = self
602 .starting_list
603 .iter()
604 .map(|l| l.to_gff_struct(false))
605 .collect();
606 upsert_field(&mut root, "StartingList", GffValue::List(start_structs));
607
608 let entry_structs: Vec<GffStruct> = self
610 .entries
611 .iter()
612 .map(|n| n.to_gff_struct("RepliesList", true, 0))
613 .collect();
614 upsert_field(&mut root, "EntryList", GffValue::List(entry_structs));
615
616 let reply_structs: Vec<GffStruct> = self
618 .replies
619 .iter()
620 .map(|n| n.to_gff_struct("EntriesList", false, 1))
621 .collect();
622 upsert_field(&mut root, "ReplyList", GffValue::List(reply_structs));
623
624 let stunt_structs: Vec<GffStruct> = self
626 .stunt_list
627 .iter()
628 .map(|st| st.to_gff_struct())
629 .collect();
630 upsert_field(&mut root, "StuntList", GffValue::List(stunt_structs));
631
632 Gff::new(*b"DLG ", root)
633 }
634}
635
636#[derive(Debug, Error)]
638pub enum DlgError {
639 #[error("unsupported DLG file type: {0:?}")]
641 UnsupportedFileType([u8; 4]),
642 #[error(transparent)]
644 Gff(#[from] GffBinaryError),
645}
646
647#[cfg_attr(
649 feature = "tracing",
650 tracing::instrument(level = "debug", skip(reader))
651)]
652pub fn read_dlg<R: Read>(reader: &mut R) -> Result<Dlg, DlgError> {
653 let gff = read_gff(reader)?;
654 Dlg::from_gff(&gff)
655}
656
657#[cfg_attr(
659 feature = "tracing",
660 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
661)]
662pub fn read_dlg_from_bytes(bytes: &[u8]) -> Result<Dlg, DlgError> {
663 let gff = read_gff_from_bytes(bytes)?;
664 Dlg::from_gff(&gff)
665}
666
667#[cfg_attr(
669 feature = "tracing",
670 tracing::instrument(level = "debug", skip(writer, dlg))
671)]
672pub fn write_dlg<W: Write>(writer: &mut W, dlg: &Dlg) -> Result<(), DlgError> {
673 let gff = dlg.to_gff();
674 write_gff(writer, &gff)?;
675 Ok(())
676}
677
678#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(dlg)))]
680pub fn write_dlg_to_vec(dlg: &Dlg) -> Result<Vec<u8>, DlgError> {
681 let mut cursor = Cursor::new(Vec::new());
682 write_dlg(&mut cursor, dlg)?;
683 Ok(cursor.into_inner())
684}
685
686static ENTRY_REPLY_LINK_CHILDREN: &[FieldSchema] = &[
692 FieldSchema {
695 label: "IsChild",
696 expected_type: GffType::UInt8,
697 life: FieldLife::ReadOnlyDead(
698 "the label string does not exist anywhere in the engine binary, so no read path can name it",
699 ),
700 required: false,
701 absent: AbsentDefault::Unverified,
702 children: None,
703 constraint: None,
704 },
705 FieldSchema {
706 label: "LinkComment",
707 expected_type: GffType::String,
708 life: FieldLife::ReadOnlyDead(
709 "the label string does not exist anywhere in the engine binary, so no read path can name it",
710 ),
711 required: false,
712 absent: AbsentDefault::Unverified,
713 children: None,
714 constraint: None,
715 },
716 FieldSchema {
717 label: "Active",
718 expected_type: GffType::ResRef,
719 life: FieldLife::Live,
720 required: false,
721 absent: AbsentDefault::Unverified,
722 children: None,
723 constraint: None,
724 },
725 FieldSchema {
726 label: "Index",
727 expected_type: GffType::UInt32,
728 life: FieldLife::Live,
729 required: false,
730 absent: AbsentDefault::Unverified,
731 children: None,
732 constraint: None,
733 },
734 FieldSchema {
735 label: "DisplayInactive",
736 expected_type: GffType::UInt8,
737 life: FieldLife::Live,
738 required: false,
739 absent: AbsentDefault::Unverified,
740 children: None,
741 constraint: None,
742 },
743];
744
745static REPLY_ENTRY_LINK_CHILDREN: &[FieldSchema] = &[
749 FieldSchema {
752 label: "IsChild",
753 expected_type: GffType::UInt8,
754 life: FieldLife::ReadOnlyDead(
755 "the label string does not exist anywhere in the engine binary, so no read path can name it",
756 ),
757 required: false,
758 absent: AbsentDefault::Unverified,
759 children: None,
760 constraint: None,
761 },
762 FieldSchema {
763 label: "LinkComment",
764 expected_type: GffType::String,
765 life: FieldLife::ReadOnlyDead(
766 "the label string does not exist anywhere in the engine binary, so no read path can name it",
767 ),
768 required: false,
769 absent: AbsentDefault::Unverified,
770 children: None,
771 constraint: None,
772 },
773 FieldSchema {
774 label: "Active",
775 expected_type: GffType::ResRef,
776 life: FieldLife::Live,
777 required: false,
778 absent: AbsentDefault::Unverified,
779 children: None,
780 constraint: None,
781 },
782 FieldSchema {
783 label: "Index",
784 expected_type: GffType::UInt32,
785 life: FieldLife::Live,
786 required: false,
787 absent: AbsentDefault::Unverified,
788 children: None,
789 constraint: None,
790 },
791];
792
793static ANIM_LIST_CHILDREN: &[FieldSchema] = &[
795 FieldSchema {
796 label: "Participant",
797 expected_type: GffType::String,
798 life: FieldLife::Live,
799 required: false,
800 absent: AbsentDefault::Unverified,
801 children: None,
802 constraint: None,
803 },
804 FieldSchema {
805 label: "Animation",
806 expected_type: GffType::UInt16,
807 life: FieldLife::Live,
808 required: false,
809 absent: AbsentDefault::Unverified,
810 children: None,
811 constraint: None,
812 },
813];
814
815static STUNT_LIST_CHILDREN: &[FieldSchema] = &[
817 FieldSchema {
818 label: "Participant",
819 expected_type: GffType::String,
820 life: FieldLife::Live,
821 required: false,
822 absent: AbsentDefault::Unverified,
823 children: None,
824 constraint: None,
825 },
826 FieldSchema {
827 label: "StuntModel",
828 expected_type: GffType::ResRef,
829 life: FieldLife::Live,
830 required: false,
831 absent: AbsentDefault::Unverified,
832 children: None,
833 constraint: None,
834 },
835];
836
837static ENTRY_NODE_CHILDREN: &[FieldSchema] = &[
843 FieldSchema {
846 label: "Comment",
847 expected_type: GffType::String,
848 life: FieldLife::ReadOnlyDead(
849 "the label string does not exist anywhere in the engine binary, so no read path can name it",
850 ),
851 required: false,
852 absent: AbsentDefault::Unverified,
853 children: None,
854 constraint: None,
855 },
856 FieldSchema {
858 label: "Text",
859 expected_type: GffType::LocalizedString,
860 life: FieldLife::Live,
861 required: false,
862 absent: AbsentDefault::Unverified,
863 children: None,
864 constraint: None,
865 },
866 FieldSchema {
867 label: "Script",
868 expected_type: GffType::ResRef,
869 life: FieldLife::Live,
870 required: false,
871 absent: AbsentDefault::Unverified,
872 children: None,
873 constraint: None,
874 },
875 FieldSchema {
876 label: "Speaker",
877 expected_type: GffType::String,
878 life: FieldLife::Live,
879 required: false,
880 absent: AbsentDefault::Unverified,
881 children: None,
882 constraint: None,
883 },
884 FieldSchema {
885 label: "WaitFlags",
886 expected_type: GffType::UInt32,
887 life: FieldLife::Live,
888 required: false,
889 absent: AbsentDefault::Unverified,
890 children: None,
891 constraint: None,
892 },
893 FieldSchema {
894 label: "Quest",
895 expected_type: GffType::String,
896 life: FieldLife::Live,
897 required: false,
898 absent: AbsentDefault::Unverified,
899 children: None,
900 constraint: None,
901 },
902 FieldSchema {
903 label: "QuestEntry",
904 expected_type: GffType::UInt32,
905 life: FieldLife::Live,
906 required: false,
907 absent: AbsentDefault::Unverified,
908 children: None,
909 constraint: None,
910 },
911 FieldSchema {
912 label: "PlotIndex",
913 expected_type: GffType::Int32,
914 life: FieldLife::Live,
915 required: false,
916 absent: AbsentDefault::Unverified,
917 children: None,
918 constraint: None,
919 },
920 FieldSchema {
921 label: "PlotXPPercentage",
922 expected_type: GffType::Single,
923 life: FieldLife::Live,
924 required: false,
925 absent: AbsentDefault::Unverified,
926 children: None,
927 constraint: None,
928 },
929 FieldSchema {
930 label: "Delay",
931 expected_type: GffType::UInt32,
932 life: FieldLife::Live,
933 required: false,
934 absent: AbsentDefault::Unverified,
935 children: None,
936 constraint: None,
937 },
938 FieldSchema {
939 label: "FadeType",
940 expected_type: GffType::UInt8,
941 life: FieldLife::Live,
942 required: false,
943 absent: AbsentDefault::Unverified,
944 children: None,
945 constraint: None,
946 },
947 FieldSchema {
948 label: "FadeColor",
949 expected_type: GffType::Vector3,
950 life: FieldLife::Live,
951 required: false,
952 absent: AbsentDefault::Unverified,
953 children: None,
954 constraint: None,
955 },
956 FieldSchema {
957 label: "FadeDelay",
958 expected_type: GffType::Single,
959 life: FieldLife::Live,
960 required: false,
961 absent: AbsentDefault::Unverified,
962 children: None,
963 constraint: None,
964 },
965 FieldSchema {
966 label: "FadeLength",
967 expected_type: GffType::Single,
968 life: FieldLife::Live,
969 required: false,
970 absent: AbsentDefault::Unverified,
971 children: None,
972 constraint: None,
973 },
974 FieldSchema {
975 label: "Sound",
976 expected_type: GffType::ResRef,
977 life: FieldLife::Live,
978 required: false,
979 absent: AbsentDefault::Unverified,
980 children: None,
981 constraint: None,
982 },
983 FieldSchema {
984 label: "VO_ResRef",
985 expected_type: GffType::ResRef,
986 life: FieldLife::Live,
987 required: false,
988 absent: AbsentDefault::Unverified,
989 children: None,
990 constraint: None,
991 },
992 FieldSchema {
993 label: "SoundExists",
994 expected_type: GffType::UInt8,
995 life: FieldLife::Live,
996 required: false,
997 absent: AbsentDefault::Unverified,
998 children: None,
999 constraint: None,
1000 },
1001 FieldSchema {
1002 label: "AnimList",
1003 expected_type: GffType::List,
1004 life: FieldLife::Live,
1005 required: false,
1006 absent: AbsentDefault::Unverified,
1007 children: Some(ANIM_LIST_CHILDREN),
1008 constraint: None,
1009 },
1010 FieldSchema {
1012 label: "Listener",
1013 expected_type: GffType::String,
1014 life: FieldLife::Live,
1015 required: false,
1016 absent: AbsentDefault::Unverified,
1017 children: None,
1018 constraint: None,
1019 },
1020 FieldSchema {
1021 label: "CameraAngle",
1022 expected_type: GffType::UInt32,
1023 life: FieldLife::Live,
1024 required: false,
1025 absent: AbsentDefault::Unverified,
1026 children: None,
1027 constraint: None,
1028 },
1029 FieldSchema {
1030 label: "CameraID",
1031 expected_type: GffType::Int32,
1032 life: FieldLife::Live,
1033 required: false,
1034 absent: AbsentDefault::Unverified,
1035 children: None,
1036 constraint: None,
1037 },
1038 FieldSchema {
1039 label: "CamHeightOffset",
1040 expected_type: GffType::Single,
1041 life: FieldLife::Live,
1042 required: false,
1043 absent: AbsentDefault::Unverified,
1044 children: None,
1045 constraint: None,
1046 },
1047 FieldSchema {
1048 label: "TarHeightOffset",
1049 expected_type: GffType::Single,
1050 life: FieldLife::Live,
1051 required: false,
1052 absent: AbsentDefault::Unverified,
1053 children: None,
1054 constraint: None,
1055 },
1056 FieldSchema {
1057 label: "CameraAnimation",
1058 expected_type: GffType::UInt16,
1059 life: FieldLife::Live,
1060 required: false,
1061 absent: AbsentDefault::Unverified,
1062 children: None,
1063 constraint: None,
1064 },
1065 FieldSchema {
1066 label: "CamVidEffect",
1067 expected_type: GffType::Int32,
1068 life: FieldLife::Live,
1069 required: false,
1070 absent: AbsentDefault::Unverified,
1071 children: None,
1072 constraint: None,
1073 },
1074 FieldSchema {
1075 label: "CamFieldOfView",
1076 expected_type: GffType::Single,
1077 life: FieldLife::Live,
1078 required: false,
1079 absent: AbsentDefault::Unverified,
1080 children: None,
1081 constraint: None,
1082 },
1083 FieldSchema {
1085 label: "RepliesList",
1086 expected_type: GffType::List,
1087 life: FieldLife::Live,
1088 required: false,
1089 absent: AbsentDefault::Unverified,
1090 children: Some(ENTRY_REPLY_LINK_CHILDREN),
1091 constraint: None,
1092 },
1093];
1094
1095static REPLY_NODE_CHILDREN: &[FieldSchema] = &[
1097 FieldSchema {
1100 label: "Comment",
1101 expected_type: GffType::String,
1102 life: FieldLife::ReadOnlyDead(
1103 "the label string does not exist anywhere in the engine binary, so no read path can name it",
1104 ),
1105 required: false,
1106 absent: AbsentDefault::Unverified,
1107 children: None,
1108 constraint: None,
1109 },
1110 FieldSchema {
1112 label: "Text",
1113 expected_type: GffType::LocalizedString,
1114 life: FieldLife::Live,
1115 required: false,
1116 absent: AbsentDefault::Unverified,
1117 children: None,
1118 constraint: None,
1119 },
1120 FieldSchema {
1121 label: "Script",
1122 expected_type: GffType::ResRef,
1123 life: FieldLife::Live,
1124 required: false,
1125 absent: AbsentDefault::Unverified,
1126 children: None,
1127 constraint: None,
1128 },
1129 FieldSchema {
1130 label: "Speaker",
1131 expected_type: GffType::String,
1132 life: FieldLife::Live,
1133 required: false,
1134 absent: AbsentDefault::Unverified,
1135 children: None,
1136 constraint: None,
1137 },
1138 FieldSchema {
1139 label: "WaitFlags",
1140 expected_type: GffType::UInt32,
1141 life: FieldLife::Live,
1142 required: false,
1143 absent: AbsentDefault::Unverified,
1144 children: None,
1145 constraint: None,
1146 },
1147 FieldSchema {
1148 label: "Quest",
1149 expected_type: GffType::String,
1150 life: FieldLife::Live,
1151 required: false,
1152 absent: AbsentDefault::Unverified,
1153 children: None,
1154 constraint: None,
1155 },
1156 FieldSchema {
1157 label: "QuestEntry",
1158 expected_type: GffType::UInt32,
1159 life: FieldLife::Live,
1160 required: false,
1161 absent: AbsentDefault::Unverified,
1162 children: None,
1163 constraint: None,
1164 },
1165 FieldSchema {
1166 label: "PlotIndex",
1167 expected_type: GffType::Int32,
1168 life: FieldLife::Live,
1169 required: false,
1170 absent: AbsentDefault::Unverified,
1171 children: None,
1172 constraint: None,
1173 },
1174 FieldSchema {
1175 label: "PlotXPPercentage",
1176 expected_type: GffType::Single,
1177 life: FieldLife::Live,
1178 required: false,
1179 absent: AbsentDefault::Unverified,
1180 children: None,
1181 constraint: None,
1182 },
1183 FieldSchema {
1184 label: "Delay",
1185 expected_type: GffType::UInt32,
1186 life: FieldLife::Live,
1187 required: false,
1188 absent: AbsentDefault::Unverified,
1189 children: None,
1190 constraint: None,
1191 },
1192 FieldSchema {
1193 label: "FadeType",
1194 expected_type: GffType::UInt8,
1195 life: FieldLife::Live,
1196 required: false,
1197 absent: AbsentDefault::Unverified,
1198 children: None,
1199 constraint: None,
1200 },
1201 FieldSchema {
1202 label: "FadeColor",
1203 expected_type: GffType::Vector3,
1204 life: FieldLife::Live,
1205 required: false,
1206 absent: AbsentDefault::Unverified,
1207 children: None,
1208 constraint: None,
1209 },
1210 FieldSchema {
1211 label: "FadeDelay",
1212 expected_type: GffType::Single,
1213 life: FieldLife::Live,
1214 required: false,
1215 absent: AbsentDefault::Unverified,
1216 children: None,
1217 constraint: None,
1218 },
1219 FieldSchema {
1220 label: "FadeLength",
1221 expected_type: GffType::Single,
1222 life: FieldLife::Live,
1223 required: false,
1224 absent: AbsentDefault::Unverified,
1225 children: None,
1226 constraint: None,
1227 },
1228 FieldSchema {
1229 label: "Sound",
1230 expected_type: GffType::ResRef,
1231 life: FieldLife::Live,
1232 required: false,
1233 absent: AbsentDefault::Unverified,
1234 children: None,
1235 constraint: None,
1236 },
1237 FieldSchema {
1238 label: "VO_ResRef",
1239 expected_type: GffType::ResRef,
1240 life: FieldLife::Live,
1241 required: false,
1242 absent: AbsentDefault::Unverified,
1243 children: None,
1244 constraint: None,
1245 },
1246 FieldSchema {
1247 label: "SoundExists",
1248 expected_type: GffType::UInt8,
1249 life: FieldLife::Live,
1250 required: false,
1251 absent: AbsentDefault::Unverified,
1252 children: None,
1253 constraint: None,
1254 },
1255 FieldSchema {
1256 label: "AnimList",
1257 expected_type: GffType::List,
1258 life: FieldLife::Live,
1259 required: false,
1260 absent: AbsentDefault::Unverified,
1261 children: Some(ANIM_LIST_CHILDREN),
1262 constraint: None,
1263 },
1264 FieldSchema {
1266 label: "Listener",
1267 expected_type: GffType::String,
1268 life: FieldLife::Live,
1269 required: false,
1270 absent: AbsentDefault::Unverified,
1271 children: None,
1272 constraint: None,
1273 },
1274 FieldSchema {
1275 label: "CameraAngle",
1276 expected_type: GffType::UInt32,
1277 life: FieldLife::Live,
1278 required: false,
1279 absent: AbsentDefault::Unverified,
1280 children: None,
1281 constraint: None,
1282 },
1283 FieldSchema {
1284 label: "CameraID",
1285 expected_type: GffType::Int32,
1286 life: FieldLife::Live,
1287 required: false,
1288 absent: AbsentDefault::Unverified,
1289 children: None,
1290 constraint: None,
1291 },
1292 FieldSchema {
1293 label: "CamHeightOffset",
1294 expected_type: GffType::Single,
1295 life: FieldLife::Live,
1296 required: false,
1297 absent: AbsentDefault::Unverified,
1298 children: None,
1299 constraint: None,
1300 },
1301 FieldSchema {
1302 label: "TarHeightOffset",
1303 expected_type: GffType::Single,
1304 life: FieldLife::Live,
1305 required: false,
1306 absent: AbsentDefault::Unverified,
1307 children: None,
1308 constraint: None,
1309 },
1310 FieldSchema {
1311 label: "CameraAnimation",
1312 expected_type: GffType::UInt16,
1313 life: FieldLife::Live,
1314 required: false,
1315 absent: AbsentDefault::Unverified,
1316 children: None,
1317 constraint: None,
1318 },
1319 FieldSchema {
1320 label: "CamVidEffect",
1321 expected_type: GffType::Int32,
1322 life: FieldLife::Live,
1323 required: false,
1324 absent: AbsentDefault::Unverified,
1325 children: None,
1326 constraint: None,
1327 },
1328 FieldSchema {
1329 label: "CamFieldOfView",
1330 expected_type: GffType::Single,
1331 life: FieldLife::Live,
1332 required: false,
1333 absent: AbsentDefault::Unverified,
1334 children: None,
1335 constraint: None,
1336 },
1337 FieldSchema {
1339 label: "EntriesList",
1340 expected_type: GffType::List,
1341 life: FieldLife::Live,
1342 required: false,
1343 absent: AbsentDefault::Unverified,
1344 children: Some(REPLY_ENTRY_LINK_CHILDREN),
1345 constraint: None,
1346 },
1347];
1348
1349impl GffSchema for Dlg {
1350 fn schema() -> &'static [FieldSchema] {
1351 static SCHEMA: &[FieldSchema] = &[
1352 FieldSchema {
1355 label: "NumWords",
1356 expected_type: GffType::UInt32,
1357 life: FieldLife::ReadOnlyDead(
1358 "the label string does not exist anywhere in the engine binary, so no read path can name it",
1359 ),
1360 required: false,
1361 absent: AbsentDefault::Unverified,
1362 children: None,
1363 constraint: None,
1364 },
1365 FieldSchema {
1366 label: "VO_ID",
1367 expected_type: GffType::String,
1368 life: FieldLife::ReadOnlyDead(
1369 "the label string does not exist anywhere in the engine binary, so no read path can name it",
1370 ),
1371 required: false,
1372 absent: AbsentDefault::Unverified,
1373 children: None,
1374 constraint: None,
1375 },
1376 FieldSchema {
1378 label: "CameraModel",
1379 expected_type: GffType::ResRef,
1380 life: FieldLife::Live,
1381 required: false,
1382 absent: AbsentDefault::Unverified,
1383 children: None,
1384 constraint: None,
1385 },
1386 FieldSchema {
1387 label: "DelayEntry",
1388 expected_type: GffType::UInt32,
1389 life: FieldLife::Live,
1390 required: false,
1391 absent: AbsentDefault::Unverified,
1392 children: None,
1393 constraint: None,
1394 },
1395 FieldSchema {
1396 label: "DelayReply",
1397 expected_type: GffType::UInt32,
1398 life: FieldLife::Live,
1399 required: false,
1400 absent: AbsentDefault::Unverified,
1401 children: None,
1402 constraint: None,
1403 },
1404 FieldSchema {
1405 label: "EndConversation",
1406 expected_type: GffType::ResRef,
1407 life: FieldLife::Live,
1408 required: false,
1409 absent: AbsentDefault::Unverified,
1410 children: None,
1411 constraint: None,
1412 },
1413 FieldSchema {
1414 label: "EndConverAbort",
1415 expected_type: GffType::ResRef,
1416 life: FieldLife::Live,
1417 required: false,
1418 absent: AbsentDefault::Unverified,
1419 children: None,
1420 constraint: None,
1421 },
1422 FieldSchema {
1423 label: "Skippable",
1424 expected_type: GffType::UInt8,
1425 life: FieldLife::Live,
1426 required: false,
1427 absent: AbsentDefault::Unverified,
1428 children: None,
1429 constraint: None,
1430 },
1431 FieldSchema {
1432 label: "ConversationType",
1433 expected_type: GffType::Int32,
1434 life: FieldLife::Live,
1435 required: false,
1436 absent: AbsentDefault::Unverified,
1437 children: None,
1438 constraint: None,
1439 },
1440 FieldSchema {
1441 label: "ComputerType",
1442 expected_type: GffType::UInt8,
1443 life: FieldLife::Live,
1444 required: false,
1445 absent: AbsentDefault::Unverified,
1446 children: None,
1447 constraint: None,
1448 },
1449 FieldSchema {
1450 label: "AmbientTrack",
1451 expected_type: GffType::ResRef,
1452 life: FieldLife::Live,
1453 required: false,
1454 absent: AbsentDefault::Unverified,
1455 children: None,
1456 constraint: None,
1457 },
1458 FieldSchema {
1459 label: "UnequipItems",
1460 expected_type: GffType::UInt8,
1461 life: FieldLife::Live,
1462 required: false,
1463 absent: AbsentDefault::Unverified,
1464 children: None,
1465 constraint: None,
1466 },
1467 FieldSchema {
1468 label: "UnequipHItem",
1469 expected_type: GffType::UInt8,
1470 life: FieldLife::Live,
1471 required: false,
1472 absent: AbsentDefault::Unverified,
1473 children: None,
1474 constraint: None,
1475 },
1476 FieldSchema {
1477 label: "AnimatedCut",
1478 expected_type: GffType::UInt8,
1479 life: FieldLife::Live,
1480 required: false,
1481 absent: AbsentDefault::Unverified,
1482 children: None,
1483 constraint: None,
1484 },
1485 FieldSchema {
1486 label: "OldHitCheck",
1487 expected_type: GffType::UInt8,
1488 life: FieldLife::Live,
1489 required: false,
1490 absent: AbsentDefault::Unverified,
1491 children: None,
1492 constraint: None,
1493 },
1494 FieldSchema {
1496 label: "EntryList",
1497 expected_type: GffType::List,
1498 life: FieldLife::Live,
1499 required: false,
1500 absent: AbsentDefault::Unverified,
1501 children: Some(ENTRY_NODE_CHILDREN),
1502 constraint: None,
1503 },
1504 FieldSchema {
1505 label: "ReplyList",
1506 expected_type: GffType::List,
1507 life: FieldLife::Live,
1508 required: false,
1509 absent: AbsentDefault::Unverified,
1510 children: Some(REPLY_NODE_CHILDREN),
1511 constraint: None,
1512 },
1513 FieldSchema {
1514 label: "StartingList",
1515 expected_type: GffType::List,
1516 life: FieldLife::Live,
1517 required: false,
1518 absent: AbsentDefault::Unverified,
1519 children: Some(REPLY_ENTRY_LINK_CHILDREN),
1520 constraint: None,
1521 },
1522 FieldSchema {
1523 label: "StuntList",
1524 expected_type: GffType::List,
1525 life: FieldLife::Live,
1526 required: false,
1527 absent: AbsentDefault::Unverified,
1528 children: Some(STUNT_LIST_CHILDREN),
1529 constraint: None,
1530 },
1531 ];
1532 SCHEMA
1533 }
1534}
1535
1536#[cfg(test)]
1537mod tests {
1538 use super::*;
1539
1540 #[test]
1541 fn display_inactive_is_omitted_at_its_default_and_written_otherwise() {
1542 let cleared = DlgLink::default();
1548 assert!(!cleared.display_inactive);
1549 assert!(cleared
1550 .to_gff_struct(true)
1551 .field("DisplayInactive")
1552 .is_none());
1553
1554 let set = DlgLink {
1555 display_inactive: true,
1556 ..DlgLink::default()
1557 };
1558 let written = set.to_gff_struct(true);
1559 assert_eq!(written.field("DisplayInactive"), Some(&GffValue::UInt8(1)));
1560 assert!(DlgLink::from_gff_struct(&written).display_inactive);
1561 }
1562
1563 fn make_test_dlg_gff() -> Gff {
1565 let mut root = GffStruct::new(-1);
1566 root.push_field("CameraModel", GffValue::resref_lit(""));
1567 root.push_field("DelayEntry", GffValue::UInt32(0));
1568 root.push_field("DelayReply", GffValue::UInt32(0));
1569 root.push_field("EndConversation", GffValue::resref_lit("k_end_conv"));
1570 root.push_field("EndConverAbort", GffValue::resref_lit("k_end_abort"));
1571 root.push_field("Skippable", GffValue::UInt8(1));
1572 root.push_field("ConversationType", GffValue::Int32(0));
1573 root.push_field("ComputerType", GffValue::UInt8(0));
1574 root.push_field("AmbientTrack", GffValue::resref_lit(""));
1575 root.push_field("UnequipItems", GffValue::UInt8(0));
1576 root.push_field("UnequipHItem", GffValue::UInt8(0));
1577 root.push_field("AnimatedCut", GffValue::UInt8(1));
1578 root.push_field("OldHitCheck", GffValue::UInt8(0));
1579
1580 let mut start_link = GffStruct::new(0);
1582 start_link.push_field("Active", GffValue::resref_lit("k_cond_start"));
1583 start_link.push_field("Index", GffValue::UInt32(0));
1584 root.push_field("StartingList", GffValue::List(vec![start_link]));
1585
1586 let mut entry0 = GffStruct::new(0);
1588 entry0.push_field(
1589 "Text",
1590 GffValue::LocalizedString(GffLocalizedString::new(StrRef::from_raw(50000))),
1591 );
1592 entry0.push_field("Script", GffValue::resref_lit("k_entry_fire"));
1593 entry0.push_field("Speaker", GffValue::String("Bastila".into()));
1594 entry0.push_field("WaitFlags", GffValue::UInt32(0));
1595 entry0.push_field("Quest", GffValue::String("".into()));
1596 entry0.push_field("QuestEntry", GffValue::UInt32(0));
1597 entry0.push_field("PlotIndex", GffValue::Int32(-1));
1598 entry0.push_field("PlotXPPercentage", GffValue::Single(1.0));
1599 entry0.push_field("Delay", GffValue::UInt32(u32::MAX));
1600 entry0.push_field("FadeType", GffValue::UInt8(0));
1601 entry0.push_field("FadeColor", GffValue::Vector3([0.0, 0.0, 0.0]));
1602 entry0.push_field("FadeDelay", GffValue::Single(0.0));
1603 entry0.push_field("FadeLength", GffValue::Single(0.0));
1604 entry0.push_field("Sound", GffValue::resref_lit(""));
1605 entry0.push_field("VO_ResRef", GffValue::resref_lit("n_bastila_hi"));
1606 entry0.push_field("SoundExists", GffValue::UInt8(1));
1607 entry0.push_field("Listener", GffValue::String("".into()));
1608 entry0.push_field("CameraAngle", GffValue::UInt32(4));
1609 entry0.push_field("CameraID", GffValue::Int32(-1));
1610 entry0.push_field("CamHeightOffset", GffValue::Single(0.0));
1611 entry0.push_field("TarHeightOffset", GffValue::Single(0.0));
1612 entry0.push_field("CameraAnimation", GffValue::UInt16(0));
1613 entry0.push_field("CamVidEffect", GffValue::Int32(-1));
1614 entry0.push_field("CamFieldOfView", GffValue::Single(-1.0));
1615
1616 let mut anim = GffStruct::new(0);
1618 anim.push_field("Participant", GffValue::String("Bastila".into()));
1619 anim.push_field("Animation", GffValue::UInt16(28));
1620 entry0.push_field("AnimList", GffValue::List(vec![anim]));
1621
1622 let mut reply_link = GffStruct::new(0);
1624 reply_link.push_field("Active", GffValue::resref_lit(""));
1625 reply_link.push_field("Index", GffValue::UInt32(0));
1626 reply_link.push_field("DisplayInactive", GffValue::UInt8(1));
1627 entry0.push_field("RepliesList", GffValue::List(vec![reply_link]));
1628
1629 let mut reply0 = GffStruct::new(1);
1631 reply0.push_field(
1632 "Text",
1633 GffValue::LocalizedString(GffLocalizedString::new(StrRef::from_raw(50001))),
1634 );
1635 reply0.push_field("Script", GffValue::resref_lit("k_reply_fire"));
1636 reply0.push_field("Speaker", GffValue::String("".into()));
1637 reply0.push_field("WaitFlags", GffValue::UInt32(0));
1638 reply0.push_field("Quest", GffValue::String("".into()));
1639 reply0.push_field("QuestEntry", GffValue::UInt32(0));
1640 reply0.push_field("PlotIndex", GffValue::Int32(-1));
1641 reply0.push_field("PlotXPPercentage", GffValue::Single(1.0));
1642 reply0.push_field("Delay", GffValue::UInt32(u32::MAX));
1643 reply0.push_field("FadeType", GffValue::UInt8(0));
1644 reply0.push_field("FadeColor", GffValue::Vector3([0.0, 0.0, 0.0]));
1645 reply0.push_field("FadeDelay", GffValue::Single(0.0));
1646 reply0.push_field("FadeLength", GffValue::Single(0.0));
1647 reply0.push_field("Sound", GffValue::resref_lit(""));
1648 reply0.push_field("VO_ResRef", GffValue::resref_lit(""));
1649 reply0.push_field("SoundExists", GffValue::UInt8(0));
1650 reply0.push_field("AnimList", GffValue::List(vec![]));
1651 reply0.push_field("Listener", GffValue::String("".into()));
1652 reply0.push_field("CameraAngle", GffValue::UInt32(0));
1653 reply0.push_field("CameraID", GffValue::Int32(-1));
1654 reply0.push_field("CamHeightOffset", GffValue::Single(0.0));
1655 reply0.push_field("TarHeightOffset", GffValue::Single(0.0));
1656 reply0.push_field("CameraAnimation", GffValue::UInt16(0));
1657 reply0.push_field("CamVidEffect", GffValue::Int32(-1));
1658 reply0.push_field("CamFieldOfView", GffValue::Single(-1.0));
1659
1660 let mut entry_link = GffStruct::new(0);
1661 entry_link.push_field("Active", GffValue::resref_lit(""));
1662 entry_link.push_field("Index", GffValue::UInt32(0));
1663 reply0.push_field("EntriesList", GffValue::List(vec![entry_link]));
1664
1665 root.push_field("EntryList", GffValue::List(vec![entry0]));
1666 root.push_field("ReplyList", GffValue::List(vec![reply0]));
1667
1668 let mut stunt = GffStruct::new(0);
1670 stunt.push_field("Participant", GffValue::String("Bastila".into()));
1671 stunt.push_field("StuntModel", GffValue::resref_lit("p_bastilabb"));
1672 root.push_field("StuntList", GffValue::List(vec![stunt]));
1673
1674 Gff::new(*b"DLG ", root)
1675 }
1676
1677 #[test]
1678 fn reads_conversation_config() {
1679 let gff = make_test_dlg_gff();
1680 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1681
1682 assert_eq!(dlg.end_conversation, "k_end_conv");
1683 assert_eq!(dlg.end_conver_abort, "k_end_abort");
1684 assert!(dlg.skippable);
1685 assert_eq!(dlg.conversation_type, 0);
1686 assert!(dlg.animated_cut);
1687 assert!(!dlg.old_hit_check);
1688 }
1689
1690 #[test]
1691 fn reads_starting_list() {
1692 let gff = make_test_dlg_gff();
1693 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1694
1695 assert_eq!(dlg.starting_list.len(), 1);
1696 assert_eq!(dlg.starting_list[0].active, "k_cond_start");
1697 assert_eq!(dlg.starting_list[0].index, 0);
1698 }
1699
1700 #[test]
1701 fn reads_entry_node() {
1702 let gff = make_test_dlg_gff();
1703 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1704
1705 assert_eq!(dlg.entries.len(), 1);
1706 let entry = &dlg.entries[0];
1707 assert_eq!(entry.text.string_ref.raw(), 50000);
1708 assert_eq!(entry.script, "k_entry_fire");
1709 assert_eq!(entry.speaker, "Bastila");
1710 assert_eq!(entry.vo_resref, "n_bastila_hi");
1711 assert!(entry.sound_exists);
1712 assert_eq!(entry.camera_angle, 4);
1713 assert_eq!(entry.animations.len(), 1);
1714 assert_eq!(entry.animations[0].participant, "Bastila");
1715 assert_eq!(entry.animations[0].animation, 28);
1716 assert_eq!(entry.links.len(), 1);
1717 assert_eq!(entry.links[0].index, 0);
1718 assert!(entry.links[0].display_inactive);
1719 }
1720
1721 #[test]
1722 fn reads_reply_node() {
1723 let gff = make_test_dlg_gff();
1724 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1725
1726 assert_eq!(dlg.replies.len(), 1);
1727 let reply = &dlg.replies[0];
1728 assert_eq!(reply.text.string_ref.raw(), 50001);
1729 assert_eq!(reply.script, "k_reply_fire");
1730 assert!(!reply.sound_exists);
1731 assert_eq!(reply.links.len(), 1);
1732 assert_eq!(reply.links[0].index, 0);
1733 }
1734
1735 #[test]
1736 fn reads_stunt_list() {
1737 let gff = make_test_dlg_gff();
1738 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1739
1740 assert_eq!(dlg.stunt_list.len(), 1);
1741 assert_eq!(dlg.stunt_list[0].participant, "Bastila");
1742 assert_eq!(dlg.stunt_list[0].stunt_model, "p_bastilabb");
1743 }
1744
1745 #[test]
1746 fn all_fields_survive_typed_roundtrip() {
1747 let gff = make_test_dlg_gff();
1748 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1749 let bytes = write_dlg_to_vec(&dlg).expect("write succeeds");
1750 let reparsed = read_dlg_from_bytes(&bytes).expect("reparse succeeds");
1751
1752 assert_eq!(dlg, reparsed);
1753 }
1754
1755 #[test]
1756 fn fade_color_roundtrips_as_vector3() {
1757 let mut gff = make_test_dlg_gff();
1758 if let Some(GffValue::List(ref mut entries)) = gff
1760 .root
1761 .fields
1762 .iter_mut()
1763 .find(|f| f.label == "EntryList")
1764 .map(|f| &mut f.value)
1765 {
1766 if let Some(field) = entries[0]
1768 .fields
1769 .iter_mut()
1770 .find(|f| f.label == "FadeColor")
1771 {
1772 field.value = GffValue::Vector3([1.0, 0.0, 0.0]);
1773 }
1774 }
1775
1776 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1777 assert_eq!(dlg.entries[0].fade_color, [1.0, 0.0, 0.0]);
1778
1779 let bytes = write_dlg_to_vec(&dlg).expect("write succeeds");
1780 let reparsed = read_dlg_from_bytes(&bytes).expect("reparse succeeds");
1781 assert_eq!(reparsed.entries[0].fade_color, [1.0, 0.0, 0.0]);
1782
1783 let rebuilt = dlg.to_gff();
1785 if let Some(GffValue::List(entries)) = rebuilt.root.field("EntryList") {
1786 let fade = entries[0]
1787 .field("FadeColor")
1788 .expect("FadeColor must be present");
1789 assert!(
1790 matches!(fade, GffValue::Vector3([1.0, 0.0, 0.0])),
1791 "FadeColor must be GffValue::Vector3, got {fade:?}"
1792 );
1793 } else {
1794 panic!("EntryList missing from rebuilt GFF");
1795 }
1796 }
1797
1798 #[test]
1799 fn typed_edits_roundtrip_through_gff_writer() {
1800 let gff = make_test_dlg_gff();
1801 let mut dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1802 dlg.end_conversation = ResRef::new("k_end_new").expect("valid test resref");
1803 dlg.entries[0].speaker = "Carth".into();
1804 dlg.entries[0].links[0].display_inactive = false;
1805
1806 let bytes = write_dlg_to_vec(&dlg).expect("write succeeds");
1807 let reparsed = read_dlg_from_bytes(&bytes).expect("reparse succeeds");
1808
1809 assert_eq!(reparsed.end_conversation, "k_end_new");
1810 assert_eq!(reparsed.entries[0].speaker, "Carth");
1811 assert!(!reparsed.entries[0].links[0].display_inactive);
1812 }
1813
1814 #[test]
1815 fn read_dlg_from_reader_matches_bytes_path() {
1816 let gff = make_test_dlg_gff();
1817 let bytes = {
1818 let mut c = Cursor::new(Vec::new());
1819 write_gff(&mut c, &gff).expect("test GFF serializes cleanly");
1820 c.into_inner()
1821 };
1822
1823 let mut cursor = Cursor::new(&bytes);
1824 let via_reader = read_dlg(&mut cursor).expect("reader parse succeeds");
1825 let via_bytes = read_dlg_from_bytes(&bytes).expect("bytes parse succeeds");
1826
1827 assert_eq!(via_reader, via_bytes);
1828 }
1829
1830 #[test]
1831 fn rejects_non_dlg_file_type() {
1832 let mut gff = make_test_dlg_gff();
1833 gff.file_type = *b"UTT ";
1834
1835 let err = Dlg::from_gff(&gff).expect_err("UTT must be rejected as DLG input");
1836 assert!(matches!(
1837 err,
1838 DlgError::UnsupportedFileType(file_type) if file_type == *b"UTT "
1839 ));
1840 }
1841
1842 #[test]
1843 fn write_dlg_matches_direct_gff_writer() {
1844 let gff = make_test_dlg_gff();
1845 let dlg = Dlg::from_gff(&gff).expect("test GFF is well-formed");
1846
1847 let via_typed = write_dlg_to_vec(&dlg).expect("typed write succeeds");
1848
1849 let mut direct = Cursor::new(Vec::new());
1850 write_gff(&mut direct, &dlg.to_gff()).expect("direct write succeeds");
1851
1852 assert_eq!(via_typed, direct.into_inner());
1853 }
1854
1855 #[test]
1856 fn schema_field_count() {
1857 assert_eq!(Dlg::schema().len(), 19);
1858 }
1859
1860 #[test]
1861 fn schema_no_duplicate_labels() {
1862 let schema = Dlg::schema();
1863 let mut labels: Vec<&str> = schema.iter().map(|f| f.label).collect();
1864 labels.sort();
1865 let before = labels.len();
1866 labels.dedup();
1867 assert_eq!(before, labels.len(), "duplicate labels in DLG schema");
1868 }
1869
1870 #[test]
1871 fn schema_entry_node_count() {
1872 let entry_list = Dlg::schema()
1873 .iter()
1874 .find(|f| f.label == "EntryList")
1875 .expect("EntryList must exist");
1876 let children = entry_list.children.expect("EntryList must have children");
1877 assert_eq!(children.len(), 27);
1878 }
1879
1880 #[test]
1881 fn schema_reply_node_count() {
1882 let reply_list = Dlg::schema()
1883 .iter()
1884 .find(|f| f.label == "ReplyList")
1885 .expect("ReplyList must exist");
1886 let children = reply_list.children.expect("ReplyList must have children");
1887 assert_eq!(children.len(), 27);
1888 }
1889}