1#![forbid(unsafe_code)]
2#![doc = "# nwnrs-git\n\nTyped parser for Neverwinter Nights area instance (`GIT`) resources.\n\n## Why This Crate Exists\n\n`GFF` is a general-purpose container; `GIT` is domain-specific. The raw GFF\nlayer has no knowledge of placed instances, instance types, or area geometry.\nThis crate lifts raw GFF structs into typed Rust collections so area tooling\ncan work with creatures, doors, placeables, and waypoints directly instead of\nnavigating untyped field maps.\n\n## Scope\n\n- parse `GIT` payloads into typed instance collections such as creatures,\n doors, placeables, triggers, sounds, and waypoints\n- preserve the original raw `GFF` structures alongside the typed view\n- expose geometry and transform data in forms suitable for higher-level tools\n- rebuild and write typed `GIT` payloads back to `GFF`\n\nThe principal entry points are [`read_git`], [`build_git_root`], [`write_git`],\nand [`GitFile`].\n\n## Invariants\n\n- authored instance order is preserved within each typed collection\n- raw top-level and per-instance `GFF` data remain available through the typed\n model\n- geometry points and transforms are represented explicitly rather than being\n folded into ad hoc tuples\n- rebuilding a `GIT` payload preserves unknown per-entry raw fields while\n rewriting the typed fields owned by this crate\n\n## See also\n\n- [`nwnrs-gff`](https://docs.rs/nwnrs-gff), the underlying typed GFF container\n layer\n- [`nwnrs-set`](https://docs.rs/nwnrs-set), which describes tileset structure\n rather than placed instances\n"include_str!("../README.md")]
3
4use std::{
5 fmt,
6 fs::File,
7 io::{self, Read, Seek, Write},
8 path::Path,
9};
10
11use nwnrs_gff::prelude::*;
12use nwnrs_resman::prelude::*;
13use nwnrs_resref::prelude::ResolvedResRef;
14use nwnrs_restype::prelude::*;
15use tracing::instrument;
16
17pub const GIT_RES_TYPE: ResType = ResType(2023);
19
20#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
GitError::Io(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Io",
&__self_0),
GitError::Gff(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Gff",
&__self_0),
GitError::ResMan(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "ResMan",
&__self_0),
GitError::Message(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Message", &__self_0),
}
}
}Debug)]
28pub enum GitError {
29 Io(io::Error),
31 Gff(GffError),
33 ResMan(ResManError),
35 Message(String),
37}
38
39impl GitError {
40 pub fn msg(message: impl Into<String>) -> Self {
48 Self::Message(message.into())
49 }
50}
51
52impl fmt::Display for GitError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::Io(error) => error.fmt(f),
56 Self::Gff(error) => error.fmt(f),
57 Self::ResMan(error) => error.fmt(f),
58 Self::Message(message) => f.write_str(message),
59 }
60 }
61}
62
63impl std::error::Error for GitError {}
64
65impl From<io::Error> for GitError {
66 fn from(value: io::Error) -> Self {
67 Self::Io(value)
68 }
69}
70
71impl From<GffError> for GitError {
72 fn from(value: GffError) -> Self {
73 Self::Gff(value)
74 }
75}
76
77impl From<ResManError> for GitError {
78 fn from(value: ResManError) -> Self {
79 Self::ResMan(value)
80 }
81}
82
83pub type GitResult<T> = Result<T, GitError>;
85
86#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitFile {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["area_properties", "creatures", "doors", "encounters",
"legacy_list", "sounds", "stores", "triggers", "waypoints",
"placeables"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.area_properties, &self.creatures, &self.doors,
&self.encounters, &self.legacy_list, &self.sounds,
&self.stores, &self.triggers, &self.waypoints,
&&self.placeables];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitFile",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitFile {
#[inline]
fn clone(&self) -> GitFile {
GitFile {
area_properties: ::core::clone::Clone::clone(&self.area_properties),
creatures: ::core::clone::Clone::clone(&self.creatures),
doors: ::core::clone::Clone::clone(&self.doors),
encounters: ::core::clone::Clone::clone(&self.encounters),
legacy_list: ::core::clone::Clone::clone(&self.legacy_list),
sounds: ::core::clone::Clone::clone(&self.sounds),
stores: ::core::clone::Clone::clone(&self.stores),
triggers: ::core::clone::Clone::clone(&self.triggers),
waypoints: ::core::clone::Clone::clone(&self.waypoints),
placeables: ::core::clone::Clone::clone(&self.placeables),
}
}
}Clone, #[automatically_derived]
impl ::core::default::Default for GitFile {
#[inline]
fn default() -> GitFile {
GitFile {
area_properties: ::core::default::Default::default(),
creatures: ::core::default::Default::default(),
doors: ::core::default::Default::default(),
encounters: ::core::default::Default::default(),
legacy_list: ::core::default::Default::default(),
sounds: ::core::default::Default::default(),
stores: ::core::default::Default::default(),
triggers: ::core::default::Default::default(),
waypoints: ::core::default::Default::default(),
placeables: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for GitFile {
#[inline]
fn eq(&self, other: &GitFile) -> bool {
self.area_properties == other.area_properties &&
self.creatures == other.creatures &&
self.doors == other.doors &&
self.encounters == other.encounters &&
self.legacy_list == other.legacy_list &&
self.sounds == other.sounds && self.stores == other.stores
&& self.triggers == other.triggers &&
self.waypoints == other.waypoints &&
self.placeables == other.placeables
}
}PartialEq)]
100pub struct GitFile {
101 pub area_properties: Option<GitAreaProperties>,
103 pub creatures: Vec<GitCreature>,
105 pub doors: Vec<GitDoor>,
107 pub encounters: Vec<GitEncounter>,
109 pub legacy_list: Vec<GffStruct>,
111 pub sounds: Vec<GitSound>,
113 pub stores: Vec<GitStore>,
115 pub triggers: Vec<GitTrigger>,
117 pub waypoints: Vec<GitWaypoint>,
119 pub placeables: Vec<GitPlaceable>,
121}
122
123impl GitFile {
124 pub fn from_file(path: impl AsRef<Path>) -> GitResult<Self> {
136 let mut file = File::open(path.as_ref())?;
137 read_git(&mut file)
138 }
139
140 pub fn from_res(res: &Res, cache_policy: CachePolicy) -> GitResult<Self> {
153 if res.resref().res_type() != GIT_RES_TYPE {
154 return Err(GitError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected git resource, got {0}",
res.resref()))
})format!(
155 "expected git resource, got {}",
156 res.resref()
157 )));
158 }
159
160 let bytes = res.read_all(cache_policy)?;
161 let mut cursor = io::Cursor::new(bytes);
162 read_git(&mut cursor)
163 }
164
165 pub fn from_resman(
177 resman: &mut ResMan,
178 area_name: &str,
179 cache_policy: CachePolicy,
180 ) -> GitResult<Self> {
181 let resolved = ResolvedResRef::from_filename(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}.git", area_name))
})format!("{area_name}.git"))
182 .map_err(|error| GitError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("git resref: {0}", error))
})format!("git resref: {error}")))?;
183 let res = resman
184 .get_resolved(&resolved)
185 .ok_or_else(|| GitError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("git not found in ResMan: {0}",
resolved))
})format!("git not found in ResMan: {resolved}")))?;
186 Self::from_res(&res, cache_policy)
187 }
188}
189
190#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitAreaProperties {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "ambient_sound_day", "ambient_sound_night",
"ambient_sound_day_volume", "ambient_sound_night_volume",
"env_audio", "music_battle", "music_day", "music_night",
"music_delay"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.ambient_sound_day, &self.ambient_sound_night,
&self.ambient_sound_day_volume,
&self.ambient_sound_night_volume, &self.env_audio,
&self.music_battle, &self.music_day, &self.music_night,
&&self.music_delay];
::core::fmt::Formatter::debug_struct_fields_finish(f,
"GitAreaProperties", names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitAreaProperties {
#[inline]
fn clone(&self) -> GitAreaProperties {
GitAreaProperties {
raw: ::core::clone::Clone::clone(&self.raw),
ambient_sound_day: ::core::clone::Clone::clone(&self.ambient_sound_day),
ambient_sound_night: ::core::clone::Clone::clone(&self.ambient_sound_night),
ambient_sound_day_volume: ::core::clone::Clone::clone(&self.ambient_sound_day_volume),
ambient_sound_night_volume: ::core::clone::Clone::clone(&self.ambient_sound_night_volume),
env_audio: ::core::clone::Clone::clone(&self.env_audio),
music_battle: ::core::clone::Clone::clone(&self.music_battle),
music_day: ::core::clone::Clone::clone(&self.music_day),
music_night: ::core::clone::Clone::clone(&self.music_night),
music_delay: ::core::clone::Clone::clone(&self.music_delay),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitAreaProperties {
#[inline]
fn eq(&self, other: &GitAreaProperties) -> bool {
self.raw == other.raw &&
self.ambient_sound_day == other.ambient_sound_day &&
self.ambient_sound_night == other.ambient_sound_night &&
self.ambient_sound_day_volume ==
other.ambient_sound_day_volume &&
self.ambient_sound_night_volume ==
other.ambient_sound_night_volume &&
self.env_audio == other.env_audio &&
self.music_battle == other.music_battle &&
self.music_day == other.music_day &&
self.music_night == other.music_night &&
self.music_delay == other.music_delay
}
}PartialEq)]
198pub struct GitAreaProperties {
199 pub raw: GffStruct,
201 pub ambient_sound_day: Option<i32>,
203 pub ambient_sound_night: Option<i32>,
205 pub ambient_sound_day_volume: Option<i32>,
207 pub ambient_sound_night_volume: Option<i32>,
209 pub env_audio: Option<i32>,
211 pub music_battle: Option<i32>,
213 pub music_day: Option<i32>,
215 pub music_night: Option<i32>,
217 pub music_delay: Option<i32>,
219}
220
221#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitTransform {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["x", "y", "z", "bearing", "x_orientation", "y_orientation"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.x, &self.y, &self.z, &self.bearing, &self.x_orientation,
&&self.y_orientation];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitTransform",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitTransform {
#[inline]
fn clone(&self) -> GitTransform {
GitTransform {
x: ::core::clone::Clone::clone(&self.x),
y: ::core::clone::Clone::clone(&self.y),
z: ::core::clone::Clone::clone(&self.z),
bearing: ::core::clone::Clone::clone(&self.bearing),
x_orientation: ::core::clone::Clone::clone(&self.x_orientation),
y_orientation: ::core::clone::Clone::clone(&self.y_orientation),
}
}
}Clone, #[automatically_derived]
impl ::core::default::Default for GitTransform {
#[inline]
fn default() -> GitTransform {
GitTransform {
x: ::core::default::Default::default(),
y: ::core::default::Default::default(),
z: ::core::default::Default::default(),
bearing: ::core::default::Default::default(),
x_orientation: ::core::default::Default::default(),
y_orientation: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for GitTransform {
#[inline]
fn eq(&self, other: &GitTransform) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z &&
self.bearing == other.bearing &&
self.x_orientation == other.x_orientation &&
self.y_orientation == other.y_orientation
}
}PartialEq)]
230pub struct GitTransform {
231 pub x: Option<f32>,
233 pub y: Option<f32>,
235 pub z: Option<f32>,
237 pub bearing: Option<f32>,
239 pub x_orientation: Option<f32>,
241 pub y_orientation: Option<f32>,
243}
244
245#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitPoint {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "GitPoint", "x",
&self.x, "y", &self.y, "z", &&self.z)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitPoint {
#[inline]
fn clone(&self) -> GitPoint {
GitPoint {
x: ::core::clone::Clone::clone(&self.x),
y: ::core::clone::Clone::clone(&self.y),
z: ::core::clone::Clone::clone(&self.z),
}
}
}Clone, #[automatically_derived]
impl ::core::default::Default for GitPoint {
#[inline]
fn default() -> GitPoint {
GitPoint {
x: ::core::default::Default::default(),
y: ::core::default::Default::default(),
z: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for GitPoint {
#[inline]
fn eq(&self, other: &GitPoint) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z
}
}PartialEq)]
254pub struct GitPoint {
255 pub x: Option<f32>,
257 pub y: Option<f32>,
259 pub z: Option<f32>,
261}
262
263#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitCreature {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "tag", "template_resref", "localized_name",
"description", "transform"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.tag, &self.template_resref,
&self.localized_name, &self.description, &&self.transform];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitCreature",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitCreature {
#[inline]
fn clone(&self) -> GitCreature {
GitCreature {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
description: ::core::clone::Clone::clone(&self.description),
transform: ::core::clone::Clone::clone(&self.transform),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitCreature {
#[inline]
fn eq(&self, other: &GitCreature) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.template_resref == other.template_resref &&
self.localized_name == other.localized_name &&
self.description == other.description &&
self.transform == other.transform
}
}PartialEq)]
271pub struct GitCreature {
272 pub raw: GffStruct,
274 pub tag: Option<String>,
276 pub template_resref: Option<String>,
278 pub localized_name: Option<GffCExoLocString>,
280 pub description: Option<GffCExoLocString>,
282 pub transform: GitTransform,
284}
285
286#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitDoor {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "tag", "localized_name", "description",
"template_resref", "appearance", "animation_state",
"linked_to", "transform"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.tag, &self.localized_name, &self.description,
&self.template_resref, &self.appearance,
&self.animation_state, &self.linked_to, &&self.transform];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitDoor",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitDoor {
#[inline]
fn clone(&self) -> GitDoor {
GitDoor {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
description: ::core::clone::Clone::clone(&self.description),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
appearance: ::core::clone::Clone::clone(&self.appearance),
animation_state: ::core::clone::Clone::clone(&self.animation_state),
linked_to: ::core::clone::Clone::clone(&self.linked_to),
transform: ::core::clone::Clone::clone(&self.transform),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitDoor {
#[inline]
fn eq(&self, other: &GitDoor) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.description == other.description &&
self.template_resref == other.template_resref &&
self.appearance == other.appearance &&
self.animation_state == other.animation_state &&
self.linked_to == other.linked_to &&
self.transform == other.transform
}
}PartialEq)]
294pub struct GitDoor {
295 pub raw: GffStruct,
297 pub tag: Option<String>,
299 pub localized_name: Option<GffCExoLocString>,
301 pub description: Option<GffCExoLocString>,
303 pub template_resref: Option<String>,
305 pub appearance: Option<i32>,
307 pub animation_state: Option<i32>,
309 pub linked_to: Option<String>,
311 pub transform: GitTransform,
313}
314
315#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitEncounter {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "GitEncounter",
"raw", &self.raw, "tag", &self.tag, "localized_name",
&self.localized_name, "transform", &self.transform, "geometry",
&&self.geometry)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitEncounter {
#[inline]
fn clone(&self) -> GitEncounter {
GitEncounter {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
transform: ::core::clone::Clone::clone(&self.transform),
geometry: ::core::clone::Clone::clone(&self.geometry),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitEncounter {
#[inline]
fn eq(&self, other: &GitEncounter) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.transform == other.transform &&
self.geometry == other.geometry
}
}PartialEq)]
323pub struct GitEncounter {
324 pub raw: GffStruct,
326 pub tag: Option<String>,
328 pub localized_name: Option<GffCExoLocString>,
330 pub transform: GitTransform,
332 pub geometry: Vec<GitPoint>,
334}
335
336#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitSoundRef {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field1_finish(f, "GitSoundRef",
"sound", &&self.sound)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitSoundRef {
#[inline]
fn clone(&self) -> GitSoundRef {
GitSoundRef { sound: ::core::clone::Clone::clone(&self.sound) }
}
}Clone, #[automatically_derived]
impl ::core::default::Default for GitSoundRef {
#[inline]
fn default() -> GitSoundRef {
GitSoundRef { sound: ::core::default::Default::default() }
}
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for GitSoundRef {
#[inline]
fn eq(&self, other: &GitSoundRef) -> bool { self.sound == other.sound }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for GitSoundRef {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<Option<String>>;
}
}Eq)]
345pub struct GitSoundRef {
346 pub sound: Option<String>,
348}
349
350#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitSound {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "tag", "localized_name", "template_resref", "transform",
"positional", "min_distance", "max_distance", "volume",
"sounds"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.tag, &self.localized_name,
&self.template_resref, &self.transform, &self.positional,
&self.min_distance, &self.max_distance, &self.volume,
&&self.sounds];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitSound",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitSound {
#[inline]
fn clone(&self) -> GitSound {
GitSound {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
transform: ::core::clone::Clone::clone(&self.transform),
positional: ::core::clone::Clone::clone(&self.positional),
min_distance: ::core::clone::Clone::clone(&self.min_distance),
max_distance: ::core::clone::Clone::clone(&self.max_distance),
volume: ::core::clone::Clone::clone(&self.volume),
sounds: ::core::clone::Clone::clone(&self.sounds),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitSound {
#[inline]
fn eq(&self, other: &GitSound) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.template_resref == other.template_resref &&
self.transform == other.transform &&
self.positional == other.positional &&
self.min_distance == other.min_distance &&
self.max_distance == other.max_distance &&
self.volume == other.volume && self.sounds == other.sounds
}
}PartialEq)]
358pub struct GitSound {
359 pub raw: GffStruct,
361 pub tag: Option<String>,
363 pub localized_name: Option<GffCExoLocString>,
365 pub template_resref: Option<String>,
367 pub transform: GitTransform,
369 pub positional: Option<bool>,
371 pub min_distance: Option<f32>,
373 pub max_distance: Option<f32>,
375 pub volume: Option<i32>,
377 pub sounds: Vec<GitSoundRef>,
379}
380
381#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitStore {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "GitStore",
"raw", &self.raw, "tag", &self.tag, "localized_name",
&self.localized_name, "template_resref", &self.template_resref,
"transform", &&self.transform)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitStore {
#[inline]
fn clone(&self) -> GitStore {
GitStore {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
transform: ::core::clone::Clone::clone(&self.transform),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitStore {
#[inline]
fn eq(&self, other: &GitStore) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.template_resref == other.template_resref &&
self.transform == other.transform
}
}PartialEq)]
389pub struct GitStore {
390 pub raw: GffStruct,
392 pub tag: Option<String>,
394 pub localized_name: Option<GffCExoLocString>,
396 pub template_resref: Option<String>,
398 pub transform: GitTransform,
400}
401
402#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitTrigger {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "GitTrigger",
"raw", &self.raw, "tag", &self.tag, "localized_name",
&self.localized_name, "transform", &self.transform, "geometry",
&&self.geometry)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitTrigger {
#[inline]
fn clone(&self) -> GitTrigger {
GitTrigger {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
transform: ::core::clone::Clone::clone(&self.transform),
geometry: ::core::clone::Clone::clone(&self.geometry),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitTrigger {
#[inline]
fn eq(&self, other: &GitTrigger) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.transform == other.transform &&
self.geometry == other.geometry
}
}PartialEq)]
410pub struct GitTrigger {
411 pub raw: GffStruct,
413 pub tag: Option<String>,
415 pub localized_name: Option<GffCExoLocString>,
417 pub transform: GitTransform,
419 pub geometry: Vec<GitPoint>,
421}
422
423#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitWaypoint {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "tag", "localized_name", "description",
"template_resref", "linked_to", "appearance", "transform"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.tag, &self.localized_name, &self.description,
&self.template_resref, &self.linked_to, &self.appearance,
&&self.transform];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitWaypoint",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitWaypoint {
#[inline]
fn clone(&self) -> GitWaypoint {
GitWaypoint {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
description: ::core::clone::Clone::clone(&self.description),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
linked_to: ::core::clone::Clone::clone(&self.linked_to),
appearance: ::core::clone::Clone::clone(&self.appearance),
transform: ::core::clone::Clone::clone(&self.transform),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitWaypoint {
#[inline]
fn eq(&self, other: &GitWaypoint) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.description == other.description &&
self.template_resref == other.template_resref &&
self.linked_to == other.linked_to &&
self.appearance == other.appearance &&
self.transform == other.transform
}
}PartialEq)]
431pub struct GitWaypoint {
432 pub raw: GffStruct,
434 pub tag: Option<String>,
436 pub localized_name: Option<GffCExoLocString>,
438 pub description: Option<GffCExoLocString>,
440 pub template_resref: Option<String>,
442 pub linked_to: Option<String>,
444 pub appearance: Option<i32>,
446 pub transform: GitTransform,
448}
449
450#[derive(#[automatically_derived]
impl ::core::fmt::Debug for GitPlaceable {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["raw", "tag", "localized_name", "description",
"template_resref", "appearance", "static_object", "useable",
"has_inventory", "transform"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.raw, &self.tag, &self.localized_name, &self.description,
&self.template_resref, &self.appearance,
&self.static_object, &self.useable, &self.has_inventory,
&&self.transform];
::core::fmt::Formatter::debug_struct_fields_finish(f, "GitPlaceable",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for GitPlaceable {
#[inline]
fn clone(&self) -> GitPlaceable {
GitPlaceable {
raw: ::core::clone::Clone::clone(&self.raw),
tag: ::core::clone::Clone::clone(&self.tag),
localized_name: ::core::clone::Clone::clone(&self.localized_name),
description: ::core::clone::Clone::clone(&self.description),
template_resref: ::core::clone::Clone::clone(&self.template_resref),
appearance: ::core::clone::Clone::clone(&self.appearance),
static_object: ::core::clone::Clone::clone(&self.static_object),
useable: ::core::clone::Clone::clone(&self.useable),
has_inventory: ::core::clone::Clone::clone(&self.has_inventory),
transform: ::core::clone::Clone::clone(&self.transform),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for GitPlaceable {
#[inline]
fn eq(&self, other: &GitPlaceable) -> bool {
self.raw == other.raw && self.tag == other.tag &&
self.localized_name == other.localized_name &&
self.description == other.description &&
self.template_resref == other.template_resref &&
self.appearance == other.appearance &&
self.static_object == other.static_object &&
self.useable == other.useable &&
self.has_inventory == other.has_inventory &&
self.transform == other.transform
}
}PartialEq)]
458pub struct GitPlaceable {
459 pub raw: GffStruct,
461 pub tag: Option<String>,
463 pub localized_name: Option<GffCExoLocString>,
465 pub description: Option<GffCExoLocString>,
467 pub template_resref: Option<String>,
469 pub appearance: Option<i32>,
471 pub static_object: Option<bool>,
473 pub useable: Option<bool>,
475 pub has_inventory: Option<bool>,
477 pub transform: GitTransform,
479}
480
481#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: GitResult<GitFile> =
loop {};
return __tracing_attr_fake_return;
}
{ let root = read_gff_root(reader)?; parse_git_root(&root) }
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/lib.rs:493",
"nwnrs_git", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/lib.rs"),
::tracing_core::__macro_support::Option::Some(493u32),
::tracing_core::__macro_support::Option::Some("nwnrs_git"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err)]
494pub fn read_git<R: Read + Seek>(reader: &mut R) -> GitResult<GitFile> {
495 let root = read_gff_root(reader)?;
496 parse_git_root(&root)
497}
498
499pub fn parse_git_root(root: &GffRoot) -> GitResult<GitFile> {
511 if root.file_type != "GIT " {
512 return Err(GitError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected GIT root, got {0:?}",
root.file_type))
})format!(
513 "expected GIT root, got {:?}",
514 root.file_type
515 )));
516 }
517
518 Ok(GitFile {
519 area_properties: gff_struct(&root.root, "AreaProperties").map(parse_area_properties),
520 creatures: gff_list(&root.root, "Creature List")
521 .into_iter()
522 .flatten()
523 .map(parse_creature)
524 .collect(),
525 doors: gff_list(&root.root, "Door List")
526 .into_iter()
527 .flatten()
528 .map(parse_door)
529 .collect(),
530 encounters: gff_list(&root.root, "Encounter List")
531 .into_iter()
532 .flatten()
533 .map(parse_encounter)
534 .collect(),
535 legacy_list: gff_list(&root.root, "List")
536 .map_or_else(Vec::new, <[nwnrs_gff::GffStruct]>::to_vec),
537 sounds: gff_list(&root.root, "SoundList")
538 .into_iter()
539 .flatten()
540 .map(parse_sound)
541 .collect(),
542 stores: gff_list(&root.root, "StoreList")
543 .into_iter()
544 .flatten()
545 .map(parse_store)
546 .collect(),
547 triggers: gff_list(&root.root, "TriggerList")
548 .into_iter()
549 .flatten()
550 .map(parse_trigger)
551 .collect(),
552 waypoints: gff_list(&root.root, "WaypointList")
553 .into_iter()
554 .flatten()
555 .map(parse_waypoint)
556 .collect(),
557 placeables: gff_list(&root.root, "Placeable List")
558 .into_iter()
559 .flatten()
560 .map(parse_placeable)
561 .collect(),
562 })
563}
564
565pub fn build_git_root(git: &GitFile) -> GitResult<GffRoot> {
580 let mut root = GffRoot::new("GIT ");
581
582 if let Some(area_properties) = &git.area_properties {
583 root.put_value(
584 "AreaProperties",
585 GffValue::Struct(build_area_properties(area_properties)?),
586 )?;
587 }
588
589 put_list(
590 &mut root.root,
591 "Creature List",
592 &git.creatures,
593 build_creature,
594 )?;
595 put_list(&mut root.root, "Door List", &git.doors, build_door)?;
596 put_list(
597 &mut root.root,
598 "Encounter List",
599 &git.encounters,
600 build_encounter,
601 )?;
602 put_list(&mut root.root, "List", &git.legacy_list, |value| {
603 Ok(value.clone())
604 })?;
605 put_list(&mut root.root, "SoundList", &git.sounds, build_sound)?;
606 put_list(&mut root.root, "StoreList", &git.stores, build_store)?;
607 put_list(&mut root.root, "TriggerList", &git.triggers, build_trigger)?;
608 put_list(
609 &mut root.root,
610 "WaypointList",
611 &git.waypoints,
612 build_waypoint,
613 )?;
614 put_list(
615 &mut root.root,
616 "Placeable List",
617 &git.placeables,
618 build_placeable,
619 )?;
620
621 Ok(root)
622}
623
624#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: GitResult<()> = loop {};
return __tracing_attr_fake_return;
}
{
let root = build_git_root(git)?;
write_gff_root(writer, &root)?;
Ok(())
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/lib.rs:637",
"nwnrs_git", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/lib.rs"),
::tracing_core::__macro_support::Option::Some(637u32),
::tracing_core::__macro_support::Option::Some("nwnrs_git"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err)]
638pub fn write_git<W: Write + Seek>(writer: &mut W, git: &GitFile) -> GitResult<()> {
639 let root = build_git_root(git)?;
640 write_gff_root(writer, &root)?;
641 Ok(())
642}
643
644fn parse_area_properties(value: &GffStruct) -> GitAreaProperties {
645 GitAreaProperties {
646 raw: value.clone(),
647 ambient_sound_day: gff_i32(value, "AmbientSndDay"),
648 ambient_sound_night: gff_i32(value, "AmbientSndNight"),
649 ambient_sound_day_volume: gff_i32(value, "AmbientSndDayVol"),
650 ambient_sound_night_volume: gff_i32(value, "AmbientSndNitVol"),
651 env_audio: gff_i32(value, "EnvAudio"),
652 music_battle: gff_i32(value, "MusicBattle"),
653 music_day: gff_i32(value, "MusicDay"),
654 music_night: gff_i32(value, "MusicNight"),
655 music_delay: gff_i32(value, "MusicDelay"),
656 }
657}
658
659fn parse_creature(value: &GffStruct) -> GitCreature {
660 GitCreature {
661 raw: value.clone(),
662 tag: gff_string(value, "Tag"),
663 template_resref: gff_resref(value, "TemplateResRef"),
664 localized_name: gff_loc_string_any(value, &["LocName", "LocalizedName"]),
665 description: gff_loc_string(value, "Description"),
666 transform: parse_transform(value),
667 }
668}
669
670fn parse_door(value: &GffStruct) -> GitDoor {
671 GitDoor {
672 raw: value.clone(),
673 tag: gff_string(value, "Tag"),
674 localized_name: gff_loc_string(value, "LocName"),
675 description: gff_loc_string(value, "Description"),
676 template_resref: gff_resref(value, "TemplateResRef"),
677 appearance: gff_i32(value, "Appearance"),
678 animation_state: gff_i32(value, "AnimationState"),
679 linked_to: gff_string(value, "LinkedTo"),
680 transform: parse_transform(value),
681 }
682}
683
684fn parse_encounter(value: &GffStruct) -> GitEncounter {
685 GitEncounter {
686 raw: value.clone(),
687 tag: gff_string(value, "Tag"),
688 localized_name: gff_loc_string_any(value, &["LocName", "LocalizedName"]),
689 transform: parse_transform(value),
690 geometry: parse_geometry(value),
691 }
692}
693
694fn parse_sound(value: &GffStruct) -> GitSound {
695 let sounds = gff_list(value, "Sounds")
696 .into_iter()
697 .flatten()
698 .map(|entry| GitSoundRef {
699 sound: gff_string_any(entry, &["Sound", "SoundResRef"]),
700 })
701 .collect();
702
703 GitSound {
704 raw: value.clone(),
705 tag: gff_string(value, "Tag"),
706 localized_name: gff_loc_string(value, "LocName"),
707 template_resref: gff_resref(value, "TemplateResRef"),
708 transform: parse_transform(value),
709 positional: gff_bool(value, "Positional"),
710 min_distance: gff_f32(value, "MinDistance"),
711 max_distance: gff_f32(value, "MaxDistance"),
712 volume: gff_i32(value, "Volume"),
713 sounds,
714 }
715}
716
717fn parse_store(value: &GffStruct) -> GitStore {
718 GitStore {
719 raw: value.clone(),
720 tag: gff_string(value, "Tag"),
721 localized_name: gff_loc_string_any(value, &["LocName", "LocalizedName"]),
722 template_resref: gff_string_any(value, &["ResRef", "TemplateResRef"]),
723 transform: parse_transform(value),
724 }
725}
726
727fn parse_trigger(value: &GffStruct) -> GitTrigger {
728 GitTrigger {
729 raw: value.clone(),
730 tag: gff_string(value, "Tag"),
731 localized_name: gff_loc_string_any(value, &["LocName", "LocalizedName"]),
732 transform: parse_transform(value),
733 geometry: parse_geometry(value),
734 }
735}
736
737fn parse_waypoint(value: &GffStruct) -> GitWaypoint {
738 GitWaypoint {
739 raw: value.clone(),
740 tag: gff_string(value, "Tag"),
741 localized_name: gff_loc_string_any(value, &["LocalizedName", "LocName"]),
742 description: gff_loc_string(value, "Description"),
743 template_resref: gff_resref(value, "TemplateResRef"),
744 linked_to: gff_string(value, "LinkedTo"),
745 appearance: gff_i32(value, "Appearance"),
746 transform: parse_transform(value),
747 }
748}
749
750fn parse_placeable(value: &GffStruct) -> GitPlaceable {
751 GitPlaceable {
752 raw: value.clone(),
753 tag: gff_string(value, "Tag"),
754 localized_name: gff_loc_string(value, "LocName"),
755 description: gff_loc_string(value, "Description"),
756 template_resref: gff_resref(value, "TemplateResRef"),
757 appearance: gff_i32(value, "Appearance"),
758 static_object: gff_bool(value, "Static"),
759 useable: gff_bool(value, "Useable"),
760 has_inventory: gff_bool(value, "HasInventory"),
761 transform: parse_transform(value),
762 }
763}
764
765fn parse_transform(value: &GffStruct) -> GitTransform {
766 GitTransform {
767 x: gff_f32_any(value, &["X", "XPosition"]),
768 y: gff_f32_any(value, &["Y", "YPosition"]),
769 z: gff_f32_any(value, &["Z", "ZPosition"]),
770 bearing: gff_f32(value, "Bearing"),
771 x_orientation: gff_f32(value, "XOrientation"),
772 y_orientation: gff_f32(value, "YOrientation"),
773 }
774}
775
776fn parse_geometry(value: &GffStruct) -> Vec<GitPoint> {
777 gff_list(value, "Geometry")
778 .into_iter()
779 .flatten()
780 .map(|point| GitPoint {
781 x: gff_f32(point, "X"),
782 y: gff_f32(point, "Y"),
783 z: gff_f32(point, "Z"),
784 })
785 .collect()
786}
787
788fn gff_struct<'a>(value: &'a GffStruct, label: &str) -> Option<&'a GffStruct> {
789 match value.get_field(label)?.value() {
790 GffValue::Struct(child) => Some(child),
791 _ => None,
792 }
793}
794
795fn gff_list<'a>(value: &'a GffStruct, label: &str) -> Option<&'a [GffStruct]> {
796 match value.get_field(label)?.value() {
797 GffValue::List(items) => Some(items.as_slice()),
798 _ => None,
799 }
800}
801
802fn gff_bool(value: &GffStruct, label: &str) -> Option<bool> {
803 match value.get_field(label)?.value() {
804 GffValue::Byte(raw) => Some(*raw != 0),
805 GffValue::Char(raw) => Some(*raw != 0),
806 GffValue::Word(raw) => Some(*raw != 0),
807 GffValue::Short(raw) => Some(*raw != 0),
808 GffValue::Dword(raw) => Some(*raw != 0),
809 GffValue::Int(raw) => Some(*raw != 0),
810 _ => None,
811 }
812}
813
814fn gff_i32(value: &GffStruct, label: &str) -> Option<i32> {
815 match value.get_field(label)?.value() {
816 GffValue::Byte(raw) => Some(i32::from(*raw)),
817 GffValue::Char(raw) => Some(i32::from(*raw)),
818 GffValue::Word(raw) => Some(i32::from(*raw)),
819 GffValue::Short(raw) => Some(i32::from(*raw)),
820 GffValue::Dword(raw) => i32::try_from(*raw).ok(),
821 GffValue::Int(raw) => Some(*raw),
822 _ => None,
823 }
824}
825
826#[allow(clippy::cast_precision_loss)]
827fn gff_f32(value: &GffStruct, label: &str) -> Option<f32> {
828 match value.get_field(label)?.value() {
829 GffValue::Byte(raw) => Some(f32::from(*raw)),
830 GffValue::Char(raw) => Some(f32::from(*raw)),
831 GffValue::Word(raw) => Some(f32::from(*raw)),
832 GffValue::Short(raw) => Some(f32::from(*raw)),
833 GffValue::Dword(raw) => Some(*raw as f32),
834 GffValue::Int(raw) => Some(*raw as f32),
835 GffValue::Float(raw) => Some(*raw),
836 _ => None,
837 }
838}
839
840fn gff_string(value: &GffStruct, label: &str) -> Option<String> {
841 gff_string_any(value, &[label])
842}
843
844fn gff_string_any(value: &GffStruct, labels: &[&str]) -> Option<String> {
845 labels
846 .iter()
847 .find_map(|label| match value.get_field(label)?.value() {
848 GffValue::CExoString(raw) | GffValue::ResRef(raw) => {
849 let trimmed = raw.trim();
850 (!trimmed.is_empty()).then(|| trimmed.to_string())
851 }
852 _ => None,
853 })
854}
855
856fn gff_resref(value: &GffStruct, label: &str) -> Option<String> {
857 gff_string_any(value, &[label])
858}
859
860fn gff_loc_string(value: &GffStruct, label: &str) -> Option<GffCExoLocString> {
861 gff_loc_string_any(value, &[label])
862}
863
864fn gff_loc_string_any(value: &GffStruct, labels: &[&str]) -> Option<GffCExoLocString> {
865 labels
866 .iter()
867 .find_map(|label| match value.get_field(label)?.value() {
868 GffValue::CExoLocString(raw) => Some(raw.clone()),
869 _ => None,
870 })
871}
872
873fn gff_f32_any(value: &GffStruct, labels: &[&str]) -> Option<f32> {
874 labels.iter().find_map(|label| gff_f32(value, label))
875}
876
877fn build_area_properties(value: &GitAreaProperties) -> GitResult<GffStruct> {
878 let mut result = value.raw.clone();
879 clear_labels(
880 &mut result,
881 &[
882 "AmbientSndDay",
883 "AmbientSndNight",
884 "AmbientSndDayVol",
885 "AmbientSndNitVol",
886 "EnvAudio",
887 "MusicBattle",
888 "MusicDay",
889 "MusicNight",
890 "MusicDelay",
891 ],
892 );
893 put_i32(&mut result, "AmbientSndDay", value.ambient_sound_day)?;
894 put_i32(&mut result, "AmbientSndNight", value.ambient_sound_night)?;
895 put_i32(
896 &mut result,
897 "AmbientSndDayVol",
898 value.ambient_sound_day_volume,
899 )?;
900 put_i32(
901 &mut result,
902 "AmbientSndNitVol",
903 value.ambient_sound_night_volume,
904 )?;
905 put_i32(&mut result, "EnvAudio", value.env_audio)?;
906 put_i32(&mut result, "MusicBattle", value.music_battle)?;
907 put_i32(&mut result, "MusicDay", value.music_day)?;
908 put_i32(&mut result, "MusicNight", value.music_night)?;
909 put_i32(&mut result, "MusicDelay", value.music_delay)?;
910 Ok(result)
911}
912
913fn build_creature(value: &GitCreature) -> GitResult<GffStruct> {
914 let mut result = value.raw.clone();
915 clear_labels(
916 &mut result,
917 &[
918 "Tag",
919 "TemplateResRef",
920 "LocName",
921 "LocalizedName",
922 "Description",
923 "XPosition",
924 "YPosition",
925 "ZPosition",
926 "X",
927 "Y",
928 "Z",
929 "Bearing",
930 "XOrientation",
931 "YOrientation",
932 ],
933 );
934 put_string(&mut result, "Tag", value.tag.as_deref())?;
935 put_resref(
936 &mut result,
937 "TemplateResRef",
938 value.template_resref.as_deref(),
939 )?;
940 put_loc_string(
941 &mut result,
942 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
943 value.localized_name.as_ref(),
944 )?;
945 put_loc_string(&mut result, "Description", value.description.as_ref())?;
946 put_transform(&mut result, &value.raw, &value.transform)?;
947 Ok(result)
948}
949
950fn build_door(value: &GitDoor) -> GitResult<GffStruct> {
951 let mut result = value.raw.clone();
952 clear_labels(
953 &mut result,
954 &[
955 "Tag",
956 "LocName",
957 "LocalizedName",
958 "Description",
959 "TemplateResRef",
960 "Appearance",
961 "AnimationState",
962 "LinkedTo",
963 "XPosition",
964 "YPosition",
965 "ZPosition",
966 "X",
967 "Y",
968 "Z",
969 "Bearing",
970 "XOrientation",
971 "YOrientation",
972 ],
973 );
974 put_string(&mut result, "Tag", value.tag.as_deref())?;
975 put_loc_string(
976 &mut result,
977 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
978 value.localized_name.as_ref(),
979 )?;
980 put_loc_string(&mut result, "Description", value.description.as_ref())?;
981 put_resref(
982 &mut result,
983 "TemplateResRef",
984 value.template_resref.as_deref(),
985 )?;
986 put_i32(&mut result, "Appearance", value.appearance)?;
987 put_i32(&mut result, "AnimationState", value.animation_state)?;
988 put_string(&mut result, "LinkedTo", value.linked_to.as_deref())?;
989 put_transform(&mut result, &value.raw, &value.transform)?;
990 Ok(result)
991}
992
993fn build_encounter(value: &GitEncounter) -> GitResult<GffStruct> {
994 let mut result = value.raw.clone();
995 clear_labels(
996 &mut result,
997 &[
998 "Tag",
999 "LocName",
1000 "LocalizedName",
1001 "Geometry",
1002 "XPosition",
1003 "YPosition",
1004 "ZPosition",
1005 "X",
1006 "Y",
1007 "Z",
1008 "Bearing",
1009 "XOrientation",
1010 "YOrientation",
1011 ],
1012 );
1013 put_string(&mut result, "Tag", value.tag.as_deref())?;
1014 put_loc_string(
1015 &mut result,
1016 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
1017 value.localized_name.as_ref(),
1018 )?;
1019 put_transform(&mut result, &value.raw, &value.transform)?;
1020 put_geometry(&mut result, &value.geometry)?;
1021 Ok(result)
1022}
1023
1024fn build_sound(value: &GitSound) -> GitResult<GffStruct> {
1025 let mut result = value.raw.clone();
1026 clear_labels(
1027 &mut result,
1028 &[
1029 "Tag",
1030 "LocName",
1031 "LocalizedName",
1032 "TemplateResRef",
1033 "Positional",
1034 "MinDistance",
1035 "MaxDistance",
1036 "Volume",
1037 "Sounds",
1038 "XPosition",
1039 "YPosition",
1040 "ZPosition",
1041 "X",
1042 "Y",
1043 "Z",
1044 "Bearing",
1045 "XOrientation",
1046 "YOrientation",
1047 ],
1048 );
1049 put_string(&mut result, "Tag", value.tag.as_deref())?;
1050 put_loc_string(
1051 &mut result,
1052 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
1053 value.localized_name.as_ref(),
1054 )?;
1055 put_resref(
1056 &mut result,
1057 "TemplateResRef",
1058 value.template_resref.as_deref(),
1059 )?;
1060 put_transform(&mut result, &value.raw, &value.transform)?;
1061 put_bool(&mut result, "Positional", value.positional)?;
1062 put_f32(&mut result, "MinDistance", value.min_distance)?;
1063 put_f32(&mut result, "MaxDistance", value.max_distance)?;
1064 put_i32(&mut result, "Volume", value.volume)?;
1065
1066 let sounds = value
1067 .sounds
1068 .iter()
1069 .map(build_sound_ref)
1070 .collect::<GitResult<Vec<_>>>()?;
1071 put_list_value(&mut result, "Sounds", sounds)?;
1072 Ok(result)
1073}
1074
1075fn build_sound_ref(value: &GitSoundRef) -> GitResult<GffStruct> {
1076 let mut result = GffStruct::new(0);
1077 put_resref(&mut result, "Sound", value.sound.as_deref())?;
1078 Ok(result)
1079}
1080
1081fn build_store(value: &GitStore) -> GitResult<GffStruct> {
1082 let mut result = value.raw.clone();
1083 clear_labels(
1084 &mut result,
1085 &[
1086 "Tag",
1087 "LocName",
1088 "LocalizedName",
1089 "ResRef",
1090 "TemplateResRef",
1091 "XPosition",
1092 "YPosition",
1093 "ZPosition",
1094 "X",
1095 "Y",
1096 "Z",
1097 "Bearing",
1098 "XOrientation",
1099 "YOrientation",
1100 ],
1101 );
1102 put_string(&mut result, "Tag", value.tag.as_deref())?;
1103 put_loc_string(
1104 &mut result,
1105 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocalizedName"),
1106 value.localized_name.as_ref(),
1107 )?;
1108 put_resref(
1109 &mut result,
1110 preferred_resref_label(&value.raw, &["ResRef", "TemplateResRef"], "ResRef"),
1111 value.template_resref.as_deref(),
1112 )?;
1113 put_transform(&mut result, &value.raw, &value.transform)?;
1114 Ok(result)
1115}
1116
1117fn build_trigger(value: &GitTrigger) -> GitResult<GffStruct> {
1118 let mut result = value.raw.clone();
1119 clear_labels(
1120 &mut result,
1121 &[
1122 "Tag",
1123 "LocName",
1124 "LocalizedName",
1125 "Geometry",
1126 "XPosition",
1127 "YPosition",
1128 "ZPosition",
1129 "X",
1130 "Y",
1131 "Z",
1132 "Bearing",
1133 "XOrientation",
1134 "YOrientation",
1135 ],
1136 );
1137 put_string(&mut result, "Tag", value.tag.as_deref())?;
1138 put_loc_string(
1139 &mut result,
1140 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
1141 value.localized_name.as_ref(),
1142 )?;
1143 put_transform(&mut result, &value.raw, &value.transform)?;
1144 put_geometry(&mut result, &value.geometry)?;
1145 Ok(result)
1146}
1147
1148fn build_waypoint(value: &GitWaypoint) -> GitResult<GffStruct> {
1149 let mut result = value.raw.clone();
1150 clear_labels(
1151 &mut result,
1152 &[
1153 "Tag",
1154 "LocName",
1155 "LocalizedName",
1156 "Description",
1157 "TemplateResRef",
1158 "LinkedTo",
1159 "Appearance",
1160 "XPosition",
1161 "YPosition",
1162 "ZPosition",
1163 "X",
1164 "Y",
1165 "Z",
1166 "Bearing",
1167 "XOrientation",
1168 "YOrientation",
1169 ],
1170 );
1171 put_string(&mut result, "Tag", value.tag.as_deref())?;
1172 put_loc_string(
1173 &mut result,
1174 preferred_loc_label(&value.raw, &["LocalizedName", "LocName"], "LocalizedName"),
1175 value.localized_name.as_ref(),
1176 )?;
1177 put_loc_string(&mut result, "Description", value.description.as_ref())?;
1178 put_resref(
1179 &mut result,
1180 "TemplateResRef",
1181 value.template_resref.as_deref(),
1182 )?;
1183 put_string(&mut result, "LinkedTo", value.linked_to.as_deref())?;
1184 put_i32(&mut result, "Appearance", value.appearance)?;
1185 put_transform(&mut result, &value.raw, &value.transform)?;
1186 Ok(result)
1187}
1188
1189fn build_placeable(value: &GitPlaceable) -> GitResult<GffStruct> {
1190 let mut result = value.raw.clone();
1191 clear_labels(
1192 &mut result,
1193 &[
1194 "Tag",
1195 "LocName",
1196 "LocalizedName",
1197 "Description",
1198 "TemplateResRef",
1199 "Appearance",
1200 "Static",
1201 "Useable",
1202 "HasInventory",
1203 "XPosition",
1204 "YPosition",
1205 "ZPosition",
1206 "X",
1207 "Y",
1208 "Z",
1209 "Bearing",
1210 "XOrientation",
1211 "YOrientation",
1212 ],
1213 );
1214 put_string(&mut result, "Tag", value.tag.as_deref())?;
1215 put_loc_string(
1216 &mut result,
1217 preferred_loc_label(&value.raw, &["LocName", "LocalizedName"], "LocName"),
1218 value.localized_name.as_ref(),
1219 )?;
1220 put_loc_string(&mut result, "Description", value.description.as_ref())?;
1221 put_resref(
1222 &mut result,
1223 "TemplateResRef",
1224 value.template_resref.as_deref(),
1225 )?;
1226 put_i32(&mut result, "Appearance", value.appearance)?;
1227 put_bool(&mut result, "Static", value.static_object)?;
1228 put_bool(&mut result, "Useable", value.useable)?;
1229 put_bool(&mut result, "HasInventory", value.has_inventory)?;
1230 put_transform(&mut result, &value.raw, &value.transform)?;
1231 Ok(result)
1232}
1233
1234fn put_transform(target: &mut GffStruct, raw: &GffStruct, value: &GitTransform) -> GitResult<()> {
1235 let (x_label, y_label, z_label) = preferred_position_labels(raw);
1236 put_f32(target, x_label, value.x)?;
1237 put_f32(target, y_label, value.y)?;
1238 put_f32(target, z_label, value.z)?;
1239 put_f32(target, "Bearing", value.bearing)?;
1240 put_f32(target, "XOrientation", value.x_orientation)?;
1241 put_f32(target, "YOrientation", value.y_orientation)?;
1242 Ok(())
1243}
1244
1245fn put_geometry(target: &mut GffStruct, value: &[GitPoint]) -> GitResult<()> {
1246 let geometry = value
1247 .iter()
1248 .map(|point| {
1249 let mut result = GffStruct::new(0);
1250 put_f32(&mut result, "X", point.x)?;
1251 put_f32(&mut result, "Y", point.y)?;
1252 put_f32(&mut result, "Z", point.z)?;
1253 Ok(result)
1254 })
1255 .collect::<GitResult<Vec<_>>>()?;
1256 put_list_value(target, "Geometry", geometry)?;
1257 Ok(())
1258}
1259
1260fn preferred_position_labels(raw: &GffStruct) -> (&'static str, &'static str, &'static str) {
1261 if raw.get_field("XPosition").is_some()
1262 || raw.get_field("YPosition").is_some()
1263 || raw.get_field("ZPosition").is_some()
1264 {
1265 ("XPosition", "YPosition", "ZPosition")
1266 } else {
1267 ("X", "Y", "Z")
1268 }
1269}
1270
1271fn preferred_loc_label<'a>(raw: &GffStruct, labels: &[&'a str], fallback: &'a str) -> &'a str {
1272 labels
1273 .iter()
1274 .copied()
1275 .find(|label| raw.get_field(label).is_some())
1276 .unwrap_or(fallback)
1277}
1278
1279fn preferred_resref_label<'a>(raw: &GffStruct, labels: &[&'a str], fallback: &'a str) -> &'a str {
1280 labels
1281 .iter()
1282 .copied()
1283 .find(|label| raw.get_field(label).is_some())
1284 .unwrap_or(fallback)
1285}
1286
1287fn clear_labels(target: &mut GffStruct, labels: &[&str]) {
1288 for label in labels {
1289 let _ = target.remove(label);
1290 }
1291}
1292
1293fn put_list<T, F>(target: &mut GffStruct, label: &str, values: &[T], mut build: F) -> GitResult<()>
1294where
1295 F: FnMut(&T) -> GitResult<GffStruct>,
1296{
1297 let structs = values
1298 .iter()
1299 .map(&mut build)
1300 .collect::<GitResult<Vec<_>>>()?;
1301 put_list_value(target, label, structs)?;
1302 Ok(())
1303}
1304
1305fn put_list_value(target: &mut GffStruct, label: &str, values: Vec<GffStruct>) -> GitResult<()> {
1306 target.put_value(label, GffValue::List(values))?;
1307 Ok(())
1308}
1309
1310fn put_string(target: &mut GffStruct, label: &str, value: Option<&str>) -> GitResult<()> {
1311 if let Some(value) = value {
1312 target.put_value(label, GffValue::CExoString(value.to_string()))?;
1313 }
1314 Ok(())
1315}
1316
1317fn put_resref(target: &mut GffStruct, label: &str, value: Option<&str>) -> GitResult<()> {
1318 if let Some(value) = value {
1319 target.put_value(label, GffValue::ResRef(value.to_string()))?;
1320 }
1321 Ok(())
1322}
1323
1324fn put_loc_string(
1325 target: &mut GffStruct,
1326 label: &str,
1327 value: Option<&GffCExoLocString>,
1328) -> GitResult<()> {
1329 if let Some(value) = value {
1330 target.put_value(label, GffValue::CExoLocString(value.clone()))?;
1331 }
1332 Ok(())
1333}
1334
1335fn put_i32(target: &mut GffStruct, label: &str, value: Option<i32>) -> GitResult<()> {
1336 if let Some(value) = value {
1337 target.put_value(label, GffValue::Int(value))?;
1338 }
1339 Ok(())
1340}
1341
1342fn put_f32(target: &mut GffStruct, label: &str, value: Option<f32>) -> GitResult<()> {
1343 if let Some(value) = value {
1344 target.put_value(label, GffValue::Float(value))?;
1345 }
1346 Ok(())
1347}
1348
1349fn put_bool(target: &mut GffStruct, label: &str, value: Option<bool>) -> GitResult<()> {
1350 if let Some(value) = value {
1351 target.put_value(label, GffValue::Byte(u8::from(value)))?;
1352 }
1353 Ok(())
1354}
1355
1356pub mod prelude {
1358 pub use crate::{
1359 GIT_RES_TYPE, GitAreaProperties, GitCreature, GitDoor, GitEncounter, GitError, GitFile,
1360 GitPlaceable, GitPoint, GitResult, GitSound, GitSoundRef, GitStore, GitTransform,
1361 GitTrigger, GitWaypoint, build_git_root, parse_git_root, read_git, write_git,
1362 };
1363}
1364
1365#[allow(clippy::panic)]
1366#[cfg(test)]
1367mod tests {
1368 use std::{io::Cursor, sync::Arc};
1369
1370 use nwnrs_gff::prelude::{
1371 GffCExoLocString, GffRoot, GffStruct, GffValue, read_gff_root, write_gff_root,
1372 };
1373 use nwnrs_resman::{CachePolicy, ResContainer, ResMan};
1374 use nwnrs_resmemfile::prelude::read_resmemfile;
1375 use nwnrs_resref::ResRef;
1376
1377 use super::{GIT_RES_TYPE, GitFile, build_git_root, parse_git_root, read_git, write_git};
1378
1379 fn encode_root(root: &GffRoot) -> Vec<u8> {
1380 let mut output = Cursor::new(Vec::new());
1381 write_gff_root(&mut output, root).unwrap_or_else(|error| {
1382 panic!("encode gff: {error}");
1383 });
1384 output.into_inner()
1385 }
1386
1387 fn make_loc_string(text: &str) -> GffCExoLocString {
1388 let mut result = GffCExoLocString::default();
1389 result.entries.push((0, text.to_string()));
1390 result
1391 }
1392
1393 fn sample_git_root() -> GffRoot {
1394 let mut root = GffRoot::new("GIT ");
1395
1396 let mut area = GffStruct::new(100);
1397 area.put_value("AmbientSndDay", GffValue::Int(81))
1398 .unwrap_or_else(|error| panic!("area ambient day: {error}"));
1399 area.put_value("MusicDay", GffValue::Int(12))
1400 .unwrap_or_else(|error| panic!("area music day: {error}"));
1401 root.put_value("AreaProperties", GffValue::Struct(area))
1402 .unwrap_or_else(|error| panic!("root area properties: {error}"));
1403
1404 let mut creature = GffStruct::new(1);
1405 creature
1406 .put_value("Tag", GffValue::CExoString("orc_01".to_string()))
1407 .unwrap_or_else(|error| panic!("creature tag: {error}"));
1408 creature
1409 .put_value(
1410 "TemplateResRef",
1411 GffValue::ResRef("orcblueprint".to_string()),
1412 )
1413 .unwrap_or_else(|error| panic!("creature template: {error}"));
1414 creature
1415 .put_value("LocName", GffValue::CExoLocString(make_loc_string("Orc")))
1416 .unwrap_or_else(|error| panic!("creature loc name: {error}"));
1417 creature
1418 .put_value("XPosition", GffValue::Float(1.0))
1419 .unwrap_or_else(|error| panic!("creature x: {error}"));
1420 creature
1421 .put_value("YPosition", GffValue::Float(2.0))
1422 .unwrap_or_else(|error| panic!("creature y: {error}"));
1423 creature
1424 .put_value("ZPosition", GffValue::Float(3.0))
1425 .unwrap_or_else(|error| panic!("creature z: {error}"));
1426 root.put_value("Creature List", GffValue::List(vec![creature]))
1427 .unwrap_or_else(|error| panic!("root creature list: {error}"));
1428
1429 let mut door = GffStruct::new(2);
1430 door.put_value("Tag", GffValue::CExoString("gate".to_string()))
1431 .unwrap_or_else(|error| panic!("door tag: {error}"));
1432 door.put_value("TemplateResRef", GffValue::ResRef("door_gate".to_string()))
1433 .unwrap_or_else(|error| panic!("door template: {error}"));
1434 door.put_value("Appearance", GffValue::Int(4))
1435 .unwrap_or_else(|error| panic!("door appearance: {error}"));
1436 door.put_value("Bearing", GffValue::Float(1.57))
1437 .unwrap_or_else(|error| panic!("door bearing: {error}"));
1438 door.put_value("X", GffValue::Float(10.0))
1439 .unwrap_or_else(|error| panic!("door x: {error}"));
1440 door.put_value("Y", GffValue::Float(20.0))
1441 .unwrap_or_else(|error| panic!("door y: {error}"));
1442 door.put_value("Z", GffValue::Float(0.5))
1443 .unwrap_or_else(|error| panic!("door z: {error}"));
1444 root.put_value("Door List", GffValue::List(vec![door]))
1445 .unwrap_or_else(|error| panic!("root door list: {error}"));
1446
1447 let mut sound_ref = GffStruct::new(0);
1448 sound_ref
1449 .put_value("Sound", GffValue::ResRef("as_pl_creak1".to_string()))
1450 .unwrap_or_else(|error| panic!("sound ref: {error}"));
1451
1452 let mut sound = GffStruct::new(3);
1453 sound
1454 .put_value("Tag", GffValue::CExoString("creak".to_string()))
1455 .unwrap_or_else(|error| panic!("sound tag: {error}"));
1456 sound
1457 .put_value("Positional", GffValue::Byte(1))
1458 .unwrap_or_else(|error| panic!("sound positional: {error}"));
1459 sound
1460 .put_value("Volume", GffValue::Int(64))
1461 .unwrap_or_else(|error| panic!("sound volume: {error}"));
1462 sound
1463 .put_value("Sounds", GffValue::List(vec![sound_ref]))
1464 .unwrap_or_else(|error| panic!("sound list: {error}"));
1465 root.put_value("SoundList", GffValue::List(vec![sound]))
1466 .unwrap_or_else(|error| panic!("root sound list: {error}"));
1467
1468 let mut waypoint = GffStruct::new(4);
1469 waypoint
1470 .put_value("Tag", GffValue::CExoString("spawn0".to_string()))
1471 .unwrap_or_else(|error| panic!("waypoint tag: {error}"));
1472 waypoint
1473 .put_value(
1474 "LocalizedName",
1475 GffValue::CExoLocString(make_loc_string("Spawn")),
1476 )
1477 .unwrap_or_else(|error| panic!("waypoint loc name: {error}"));
1478 waypoint
1479 .put_value("TemplateResRef", GffValue::ResRef("spawn0".to_string()))
1480 .unwrap_or_else(|error| panic!("waypoint template: {error}"));
1481 waypoint
1482 .put_value("XPosition", GffValue::Float(5.0))
1483 .unwrap_or_else(|error| panic!("waypoint x: {error}"));
1484 waypoint
1485 .put_value("YPosition", GffValue::Float(6.0))
1486 .unwrap_or_else(|error| panic!("waypoint y: {error}"));
1487 waypoint
1488 .put_value("ZPosition", GffValue::Float(7.0))
1489 .unwrap_or_else(|error| panic!("waypoint z: {error}"));
1490 waypoint
1491 .put_value("XOrientation", GffValue::Float(0.0))
1492 .unwrap_or_else(|error| panic!("waypoint xo: {error}"));
1493 waypoint
1494 .put_value("YOrientation", GffValue::Float(1.0))
1495 .unwrap_or_else(|error| panic!("waypoint yo: {error}"));
1496 root.put_value("WaypointList", GffValue::List(vec![waypoint]))
1497 .unwrap_or_else(|error| panic!("root waypoint list: {error}"));
1498
1499 let mut placeable = GffStruct::new(5);
1500 placeable
1501 .put_value("Tag", GffValue::CExoString("chest_01".to_string()))
1502 .unwrap_or_else(|error| panic!("placeable tag: {error}"));
1503 placeable
1504 .put_value("LocName", GffValue::CExoLocString(make_loc_string("Chest")))
1505 .unwrap_or_else(|error| panic!("placeable loc name: {error}"));
1506 placeable
1507 .put_value("TemplateResRef", GffValue::ResRef("plc_chest".to_string()))
1508 .unwrap_or_else(|error| panic!("placeable template: {error}"));
1509 placeable
1510 .put_value("Appearance", GffValue::Int(99))
1511 .unwrap_or_else(|error| panic!("placeable appearance: {error}"));
1512 placeable
1513 .put_value("Static", GffValue::Byte(1))
1514 .unwrap_or_else(|error| panic!("placeable static: {error}"));
1515 placeable
1516 .put_value("Useable", GffValue::Byte(1))
1517 .unwrap_or_else(|error| panic!("placeable useable: {error}"));
1518 placeable
1519 .put_value("HasInventory", GffValue::Byte(1))
1520 .unwrap_or_else(|error| panic!("placeable inventory: {error}"));
1521 placeable
1522 .put_value("X", GffValue::Float(11.0))
1523 .unwrap_or_else(|error| panic!("placeable x: {error}"));
1524 placeable
1525 .put_value("Y", GffValue::Float(12.0))
1526 .unwrap_or_else(|error| panic!("placeable y: {error}"));
1527 placeable
1528 .put_value("Z", GffValue::Float(0.0))
1529 .unwrap_or_else(|error| panic!("placeable z: {error}"));
1530 placeable
1531 .put_value("Bearing", GffValue::Float(0.25))
1532 .unwrap_or_else(|error| panic!("placeable bearing: {error}"));
1533 root.put_value("Placeable List", GffValue::List(vec![placeable]))
1534 .unwrap_or_else(|error| panic!("root placeable list: {error}"));
1535
1536 root
1537 }
1538
1539 #[test]
1540 fn parses_typed_git_root() {
1541 let encoded = encode_root(&sample_git_root());
1542 let reparsed_root =
1543 read_gff_root(&mut Cursor::new(encoded.clone())).unwrap_or_else(|error| {
1544 panic!("re-read gff root: {error}");
1545 });
1546
1547 let parsed = parse_git_root(&reparsed_root).unwrap_or_else(|error| {
1548 panic!("parse git root: {error}");
1549 });
1550
1551 assert_eq!(
1552 parsed
1553 .area_properties
1554 .as_ref()
1555 .and_then(|value| value.ambient_sound_day),
1556 Some(81)
1557 );
1558 assert_eq!(parsed.creatures.len(), 1);
1559 assert_eq!(parsed.doors.len(), 1);
1560 assert_eq!(parsed.sounds.len(), 1);
1561 assert_eq!(parsed.waypoints.len(), 1);
1562 assert_eq!(parsed.placeables.len(), 1);
1563 assert_eq!(
1564 parsed
1565 .creatures
1566 .first()
1567 .and_then(|value| value.template_resref.as_deref()),
1568 Some("orcblueprint")
1569 );
1570 assert_eq!(
1571 parsed
1572 .doors
1573 .first()
1574 .and_then(|value| value.transform.bearing),
1575 Some(1.57)
1576 );
1577 assert_eq!(
1578 parsed
1579 .sounds
1580 .first()
1581 .and_then(|value| value.sounds.first())
1582 .and_then(|value| value.sound.as_deref()),
1583 Some("as_pl_creak1")
1584 );
1585 assert_eq!(
1586 parsed
1587 .waypoints
1588 .first()
1589 .and_then(|value| value.transform.y_orientation),
1590 Some(1.0)
1591 );
1592 assert_eq!(
1593 parsed
1594 .placeables
1595 .first()
1596 .and_then(|value| value.static_object),
1597 Some(true)
1598 );
1599
1600 let reparsed = read_git(&mut Cursor::new(encoded)).unwrap_or_else(|error| {
1601 panic!("read git: {error}");
1602 });
1603 assert_eq!(
1604 reparsed
1605 .placeables
1606 .first()
1607 .and_then(|value| value.transform.x),
1608 Some(11.0)
1609 );
1610 }
1611
1612 #[test]
1613 fn reads_git_from_resman() {
1614 let bytes = encode_root(&sample_git_root());
1615 let rr = ResRef::new("arena", GIT_RES_TYPE).unwrap_or_else(|error| {
1616 panic!("arena rr: {error}");
1617 });
1618 let resmem = read_resmemfile("arena.git", rr, bytes).unwrap_or_else(|error| {
1619 panic!("resmem file: {error}");
1620 });
1621
1622 let mut resman = ResMan::new(0);
1623 resman.add(Arc::new(resmem) as Arc<dyn ResContainer>);
1624
1625 let parsed = GitFile::from_resman(&mut resman, "arena", CachePolicy::Bypass)
1626 .unwrap_or_else(|error| {
1627 panic!("read git from resman: {error}");
1628 });
1629 assert_eq!(
1630 parsed
1631 .area_properties
1632 .as_ref()
1633 .and_then(|value| value.music_day),
1634 Some(12)
1635 );
1636 assert_eq!(
1637 parsed
1638 .placeables
1639 .first()
1640 .and_then(|value| value.template_resref.as_deref()),
1641 Some("plc_chest")
1642 );
1643 }
1644
1645 #[test]
1646 fn writes_git_round_trip_from_typed_model() {
1647 let original = read_git(&mut Cursor::new(encode_root(&sample_git_root())))
1648 .unwrap_or_else(|error| panic!("read original git: {error}"));
1649
1650 let mut encoded = Cursor::new(Vec::new());
1651 write_git(&mut encoded, &original).unwrap_or_else(|error| {
1652 panic!("write git: {error}");
1653 });
1654
1655 let reparsed = read_git(&mut Cursor::new(encoded.into_inner())).unwrap_or_else(|error| {
1656 panic!("re-read git: {error}");
1657 });
1658 assert_eq!(
1659 reparsed
1660 .area_properties
1661 .as_ref()
1662 .and_then(|value| value.music_day),
1663 original
1664 .area_properties
1665 .as_ref()
1666 .and_then(|value| value.music_day)
1667 );
1668 assert_eq!(reparsed.creatures.len(), original.creatures.len());
1669 assert_eq!(reparsed.doors.len(), original.doors.len());
1670 assert_eq!(reparsed.sounds.len(), original.sounds.len());
1671 assert_eq!(reparsed.waypoints.len(), original.waypoints.len());
1672 assert_eq!(reparsed.placeables.len(), original.placeables.len());
1673 assert_eq!(
1674 reparsed
1675 .creatures
1676 .first()
1677 .and_then(|value| value.template_resref.as_deref()),
1678 original
1679 .creatures
1680 .first()
1681 .and_then(|value| value.template_resref.as_deref())
1682 );
1683 assert_eq!(
1684 reparsed
1685 .doors
1686 .first()
1687 .and_then(|value| value.transform.bearing),
1688 original
1689 .doors
1690 .first()
1691 .and_then(|value| value.transform.bearing)
1692 );
1693 assert_eq!(
1694 reparsed
1695 .sounds
1696 .first()
1697 .and_then(|value| value.sounds.first())
1698 .and_then(|value| value.sound.as_deref()),
1699 original
1700 .sounds
1701 .first()
1702 .and_then(|value| value.sounds.first())
1703 .and_then(|value| value.sound.as_deref())
1704 );
1705 assert_eq!(
1706 reparsed
1707 .waypoints
1708 .first()
1709 .and_then(|value| value.transform.y_orientation),
1710 original
1711 .waypoints
1712 .first()
1713 .and_then(|value| value.transform.y_orientation)
1714 );
1715 assert_eq!(
1716 reparsed
1717 .placeables
1718 .first()
1719 .and_then(|value| value.static_object),
1720 original
1721 .placeables
1722 .first()
1723 .and_then(|value| value.static_object)
1724 );
1725 }
1726
1727 #[test]
1728 fn build_git_root_preserves_unknown_fields_from_raw_entries() {
1729 let mut parsed = read_git(&mut Cursor::new(encode_root(&sample_git_root())))
1730 .unwrap_or_else(|error| panic!("read original git: {error}"));
1731 parsed
1732 .creatures
1733 .first_mut()
1734 .unwrap_or_else(|| panic!("creature should exist"))
1735 .raw
1736 .put_value("CustomField", GffValue::Int(1234))
1737 .unwrap_or_else(|error| panic!("insert custom field: {error}"));
1738
1739 let root = build_git_root(&parsed).unwrap_or_else(|error| {
1740 panic!("build git root: {error}");
1741 });
1742
1743 let creature_list = match root
1744 .root
1745 .get_field("Creature List")
1746 .map(|field| field.value())
1747 {
1748 Some(GffValue::List(creatures)) => creatures,
1749 other => panic!("expected creature list, got {other:?}"),
1750 };
1751 assert_eq!(
1752 creature_list
1753 .first()
1754 .and_then(|creature| creature.get_field("CustomField"))
1755 .map(|field| field.value()),
1756 Some(&GffValue::Int(1234))
1757 );
1758 }
1759}