1pub mod minigame;
42
43use std::io::{Cursor, Read, Write};
44
45use crate::gff_helpers::{
46 get_bool, get_f32, get_i32, get_locstring, get_resref, get_string, get_u16, get_u32, get_u8,
47 upsert_field,
48};
49use rakata_core::{ResRef, StrRef};
50use rakata_formats::{
51 gff_schema::{AbsentDefault, FieldConstraint, FieldLife, FieldSchema, GffSchema, GffType},
52 read_gff, read_gff_from_bytes, write_gff, Gff, GffBinaryError, GffLocalizedString, GffStruct,
53 GffValue,
54};
55use thiserror::Error;
56
57pub use minigame::{
58 AreMiniGame, AreMiniGameBullet, AreMiniGameEnemy, AreMiniGameGunBank, AreMiniGameModel,
59 AreMiniGameMouse, AreMiniGameObjectScripts, AreMiniGameObstacle, AreMiniGamePlayer,
60 AreMiniGameSounds, AreMiniGameTargeting, AreMiniGameVehicle, AreMiniGameVehicleScripts,
61 BANK_ID_NONE,
62};
63
64#[derive(Debug, Clone, PartialEq)]
66pub struct Are {
67 pub unused_id: i32,
69 pub creator_id: i32,
71 pub version: u32,
73 pub tag: String,
75 pub name: GffLocalizedString,
77 pub comment: String,
79 pub alpha_test: f32,
81 pub camera_style: i32,
83 pub default_envmap: ResRef,
85 pub restrict_mode: u8,
87 pub grass_texture: ResRef,
89 pub grass_density: f32,
91 pub grass_size: f32,
93 pub grass_prob_ll: f32,
95 pub grass_prob_lr: f32,
97 pub grass_prob_ul: f32,
99 pub grass_prob_ur: f32,
101 pub fog_enabled: bool,
103 pub fog_near: f32,
105 pub fog_far: f32,
107 pub shadows: bool,
109 pub shadow_opacity: u8,
111 pub wind_power: i32,
113 pub unescapable: bool,
115 pub disable_transit: bool,
117 pub stealth_xp: bool,
119 pub stealth_xp_loss: u32,
121 pub stealth_xp_max: u32,
123 pub stealth_xp_current: u32,
125 pub on_enter: ResRef,
127 pub on_exit: ResRef,
129 pub on_heartbeat: ResRef,
131 pub on_user_defined: ResRef,
133 pub flags: u32,
135 pub loadscreen_id: u16,
137 pub chance_rain: i32,
139 pub chance_snow: i32,
141 pub chance_lightning: i32,
143 pub chance_fog: i32,
145 pub mod_spot_check: i32,
147 pub mod_listen_check: i32,
149 pub moon_ambient_color: u32,
151 pub moon_diffuse_color: u32,
153 pub moon_fog_enabled: bool,
155 pub moon_fog_near: f32,
157 pub moon_fog_far: f32,
159 pub moon_fog_color: u32,
161 pub moon_shadows: bool,
163 pub sun_ambient_color: u32,
165 pub sun_diffuse_color: u32,
167 pub dynamic_ambient_color: u32,
169 pub sun_fog_color: u32,
171 pub grass_ambient_color: u32,
173 pub grass_diffuse_color: u32,
175 pub grass_emissive_color: u32,
177 pub dirty_argb_one: i32,
179 pub dirty_size_one: i32,
181 pub dirty_formula_one: i32,
183 pub dirty_func_one: i32,
185 pub dirty_argb_two: i32,
187 pub dirty_size_two: i32,
189 pub dirty_formula_two: i32,
191 pub dirty_func_two: i32,
193 pub dirty_argb_three: i32,
195 pub dirty_size_three: i32,
197 pub dirty_formula_three: i32,
199 pub dirty_func_three: i32,
201 pub is_night: bool,
203 pub lighting_scheme: u8,
205 pub day_night_cycle: u8,
207 pub no_rest: bool,
209 pub no_hang_back: bool,
211 pub player_only: bool,
213 pub player_vs_player: u8,
215 pub trans_pending: u8,
217 pub trans_pend_next_id: u8,
219 pub trans_pend_curr_id: u8,
221 pub map: AreMap,
223 pub rooms: Vec<AreRoom>,
225 pub expansion_list: Vec<AreExpansionEntry>,
227 pub mini_game: Option<AreMiniGame>,
229}
230
231impl Default for Are {
232 fn default() -> Self {
233 Self {
234 unused_id: 0,
235 creator_id: 0,
236 version: 0,
237 tag: String::new(),
238 name: GffLocalizedString::new(StrRef::invalid()),
239 comment: String::new(),
240 alpha_test: 0.0,
241 camera_style: 0,
242 default_envmap: ResRef::blank(),
243 restrict_mode: 0,
244 grass_texture: ResRef::blank(),
245 grass_density: 0.0,
246 grass_size: 0.0,
247 grass_prob_ll: 0.0,
248 grass_prob_lr: 0.0,
249 grass_prob_ul: 0.0,
250 grass_prob_ur: 0.0,
251 fog_enabled: false,
252 fog_near: 0.0,
253 fog_far: 0.0,
254 shadows: false,
255 shadow_opacity: 0,
256 wind_power: 0,
257 unescapable: false,
258 disable_transit: false,
259 stealth_xp: false,
260 stealth_xp_loss: 0,
261 stealth_xp_max: 0,
262 stealth_xp_current: 0,
263 on_enter: ResRef::blank(),
264 on_exit: ResRef::blank(),
265 on_heartbeat: ResRef::blank(),
266 on_user_defined: ResRef::blank(),
267 flags: 0,
268 loadscreen_id: 0,
269 chance_rain: 0,
270 chance_snow: 0,
271 chance_lightning: 0,
272 chance_fog: 0,
273 mod_spot_check: 0,
274 mod_listen_check: 0,
275 moon_ambient_color: 0,
276 moon_diffuse_color: 0,
277 moon_fog_enabled: false,
278 moon_fog_near: 0.0,
279 moon_fog_far: 0.0,
280 moon_fog_color: 0,
281 moon_shadows: false,
282 sun_ambient_color: 0,
283 sun_diffuse_color: 0,
284 dynamic_ambient_color: 0,
285 sun_fog_color: 0,
286 grass_ambient_color: 0,
287 grass_diffuse_color: 0,
288 grass_emissive_color: 0,
289 dirty_argb_one: 0,
290 dirty_size_one: 0,
291 dirty_formula_one: 0,
292 dirty_func_one: 0,
293 dirty_argb_two: 0,
294 dirty_size_two: 0,
295 dirty_formula_two: 0,
296 dirty_func_two: 0,
297 dirty_argb_three: 0,
298 dirty_size_three: 0,
299 dirty_formula_three: 0,
300 dirty_func_three: 0,
301 is_night: false,
302 lighting_scheme: 0,
303 day_night_cycle: 0,
304 no_rest: false,
305 no_hang_back: false,
306 player_only: false,
307 player_vs_player: 0,
308 trans_pending: 0,
309 trans_pend_next_id: 0,
310 trans_pend_curr_id: 0,
311 map: AreMap::default(),
312 rooms: Vec::new(),
313 expansion_list: Vec::new(),
314 mini_game: None,
315 }
316 }
317}
318
319impl Are {
320 pub fn new() -> Self {
322 Self::default()
323 }
324
325 pub fn from_gff(gff: &Gff) -> Result<Self, AreError> {
327 if gff.file_type != *b"ARE " && gff.file_type != *b"GFF " {
328 return Err(AreError::UnsupportedFileType(gff.file_type));
329 }
330
331 let root = &gff.root;
332
333 let map = match root.field("Map") {
334 Some(GffValue::Struct(map_struct)) => AreMap::from_struct(map_struct),
335 Some(_) => {
336 return Err(AreError::TypeMismatch {
337 field: "Map",
338 expected: "Struct",
339 });
340 }
341 None => AreMap::default(),
342 };
343
344 let rooms = match root.field("Rooms") {
345 Some(GffValue::List(room_structs)) => room_structs
346 .iter()
347 .map(AreRoom::from_struct)
348 .collect::<Result<Vec<_>, _>>()?,
349 Some(_) => {
350 return Err(AreError::TypeMismatch {
351 field: "Rooms",
352 expected: "List",
353 });
354 }
355 None => Vec::new(),
356 };
357
358 let expansion_list = match root.field("Expansion_List") {
359 Some(GffValue::List(expansion_structs)) => expansion_structs
360 .iter()
361 .map(AreExpansionEntry::from_struct)
362 .collect::<Result<Vec<_>, _>>()?,
363 Some(_) => {
364 return Err(AreError::TypeMismatch {
365 field: "Expansion_List",
366 expected: "List",
367 });
368 }
369 None => Vec::new(),
370 };
371
372 let mini_game = match root.field("MiniGame") {
373 Some(GffValue::Struct(s)) => Some(AreMiniGame::from_struct(s)?),
374 Some(_) => {
375 return Err(AreError::TypeMismatch {
376 field: "MiniGame",
377 expected: "Struct",
378 });
379 }
380 None => None,
381 };
382
383 Ok(Self {
384 unused_id: get_i32(root, "ID").unwrap_or(0),
385 creator_id: get_i32(root, "Creator_ID").unwrap_or(0),
386 version: get_u32(root, "Version").unwrap_or(0),
387 tag: get_string(root, "Tag").unwrap_or_default(),
388 name: get_locstring(root, "Name")
389 .cloned()
390 .unwrap_or_else(|| GffLocalizedString::new(StrRef::invalid())),
391 comment: get_string(root, "Comments").unwrap_or_default(),
392 alpha_test: get_f32(root, "AlphaTest").unwrap_or(0.0),
393 camera_style: get_i32(root, "CameraStyle").unwrap_or(0),
394 default_envmap: get_resref(root, "DefaultEnvMap").unwrap_or_default(),
395 restrict_mode: get_u8(root, "RestrictMode").unwrap_or(0),
396 grass_texture: get_resref(root, "Grass_TexName").unwrap_or_default(),
397 grass_density: get_f32(root, "Grass_Density").unwrap_or(0.0),
398 grass_size: get_f32(root, "Grass_QuadSize").unwrap_or(0.0),
399 grass_prob_ll: get_f32(root, "Grass_Prob_LL").unwrap_or(0.0),
400 grass_prob_lr: get_f32(root, "Grass_Prob_LR").unwrap_or(0.0),
401 grass_prob_ul: get_f32(root, "Grass_Prob_UL").unwrap_or(0.0),
402 grass_prob_ur: get_f32(root, "Grass_Prob_UR").unwrap_or(0.0),
403 fog_enabled: get_bool(root, "SunFogOn").unwrap_or(false),
404 fog_near: get_f32(root, "SunFogNear").unwrap_or(0.0),
405 fog_far: get_f32(root, "SunFogFar").unwrap_or(0.0),
406 shadows: get_bool(root, "SunShadows").unwrap_or(false),
407 shadow_opacity: get_u8(root, "ShadowOpacity").unwrap_or(0),
408 wind_power: get_i32(root, "WindPower").unwrap_or(0),
409 unescapable: get_bool(root, "Unescapable").unwrap_or(false),
410 disable_transit: get_bool(root, "DisableTransit").unwrap_or(false),
411 stealth_xp: get_bool(root, "StealthXPEnabled").unwrap_or(false),
412 stealth_xp_loss: get_u32(root, "StealthXPLoss").unwrap_or(0),
413 stealth_xp_max: get_u32(root, "StealthXPMax").unwrap_or(0),
414 stealth_xp_current: get_u32(root, "StealthXPCurrent").unwrap_or(0),
415 on_enter: get_resref(root, "OnEnter").unwrap_or_default(),
416 on_exit: get_resref(root, "OnExit").unwrap_or_default(),
417 on_heartbeat: get_resref(root, "OnHeartbeat").unwrap_or_default(),
418 on_user_defined: get_resref(root, "OnUserDefined").unwrap_or_default(),
419 flags: get_u32(root, "Flags").unwrap_or(0),
420 loadscreen_id: get_u16(root, "LoadScreenID").unwrap_or(0),
421 chance_rain: get_i32(root, "ChanceRain").unwrap_or(0),
422 chance_snow: get_i32(root, "ChanceSnow").unwrap_or(0),
423 chance_lightning: get_i32(root, "ChanceLightning").unwrap_or(0),
424 chance_fog: get_i32(root, "ChanceFog").unwrap_or(0),
425 mod_spot_check: get_i32(root, "ModSpotCheck").unwrap_or(0),
426 mod_listen_check: get_i32(root, "ModListenCheck").unwrap_or(0),
427 moon_ambient_color: get_u32(root, "MoonAmbientColor").unwrap_or(0),
428 moon_diffuse_color: get_u32(root, "MoonDiffuseColor").unwrap_or(0),
429 moon_fog_enabled: get_bool(root, "MoonFogOn").unwrap_or(false),
430 moon_fog_near: get_f32(root, "MoonFogNear").unwrap_or(0.0),
431 moon_fog_far: get_f32(root, "MoonFogFar").unwrap_or(0.0),
432 moon_fog_color: get_u32(root, "MoonFogColor").unwrap_or(0),
433 moon_shadows: get_bool(root, "MoonShadows").unwrap_or(false),
434 sun_ambient_color: get_u32(root, "SunAmbientColor").unwrap_or(0),
435 sun_diffuse_color: get_u32(root, "SunDiffuseColor").unwrap_or(0),
436 dynamic_ambient_color: get_u32(root, "DynAmbientColor").unwrap_or(0),
437 sun_fog_color: get_u32(root, "SunFogColor").unwrap_or(0),
438 grass_ambient_color: get_u32(root, "Grass_Ambient").unwrap_or(0),
439 grass_diffuse_color: get_u32(root, "Grass_Diffuse").unwrap_or(0),
440 grass_emissive_color: get_u32(root, "Grass_Emissive").unwrap_or(0),
441 dirty_argb_one: get_i32(root, "DirtyARGBOne").unwrap_or(0),
442 dirty_size_one: get_i32(root, "DirtySizeOne").unwrap_or(0),
443 dirty_formula_one: get_i32(root, "DirtyFormulaOne").unwrap_or(0),
444 dirty_func_one: get_i32(root, "DirtyFuncOne").unwrap_or(0),
445 dirty_argb_two: get_i32(root, "DirtyARGBTwo").unwrap_or(0),
446 dirty_size_two: get_i32(root, "DirtySizeTwo").unwrap_or(0),
447 dirty_formula_two: get_i32(root, "DirtyFormulaTwo").unwrap_or(0),
448 dirty_func_two: get_i32(root, "DirtyFuncTwo").unwrap_or(0),
449 dirty_argb_three: get_i32(root, "DirtyARGBThree").unwrap_or(0),
450 dirty_size_three: get_i32(root, "DirtySizeThree").unwrap_or(0),
451 dirty_formula_three: get_i32(root, "DirtyFormulaThre").unwrap_or(0),
452 dirty_func_three: get_i32(root, "DirtyFuncThree").unwrap_or(0),
453 is_night: get_bool(root, "IsNight").unwrap_or(false),
454 lighting_scheme: get_u8(root, "LightingScheme").unwrap_or(0),
455 day_night_cycle: get_u8(root, "DayNightCycle").unwrap_or(0),
456 no_rest: get_bool(root, "NoRest").unwrap_or(false),
457 no_hang_back: get_bool(root, "NoHangBack").unwrap_or(false),
458 player_only: get_bool(root, "PlayerOnly").unwrap_or(false),
459 player_vs_player: get_u8(root, "PlayerVsPlayer").unwrap_or(0),
460 trans_pending: get_u8(root, "TransPending").unwrap_or(0),
461 trans_pend_next_id: get_u8(root, "TransPendNextID").unwrap_or(0),
462 trans_pend_curr_id: get_u8(root, "TransPendCurrID").unwrap_or(0),
463 map,
464 rooms,
465 expansion_list,
466 mini_game,
467 })
468 }
469
470 pub fn to_gff(&self) -> Gff {
472 let mut root = GffStruct::new(-1);
473
474 upsert_field(&mut root, "ID", GffValue::Int32(self.unused_id));
475 upsert_field(&mut root, "Creator_ID", GffValue::Int32(self.creator_id));
476 upsert_field(&mut root, "Version", GffValue::UInt32(self.version));
477 upsert_field(&mut root, "Tag", GffValue::String(self.tag.clone()));
478 upsert_field(
479 &mut root,
480 "Name",
481 GffValue::LocalizedString(self.name.clone()),
482 );
483 upsert_field(
484 &mut root,
485 "Comments",
486 GffValue::String(self.comment.clone()),
487 );
488 upsert_field(&mut root, "AlphaTest", GffValue::Single(self.alpha_test));
489 upsert_field(&mut root, "CameraStyle", GffValue::Int32(self.camera_style));
490 upsert_field(
491 &mut root,
492 "DefaultEnvMap",
493 GffValue::ResRef(self.default_envmap),
494 );
495 upsert_field(
496 &mut root,
497 "RestrictMode",
498 GffValue::UInt8(self.restrict_mode),
499 );
500 upsert_field(
501 &mut root,
502 "Grass_TexName",
503 GffValue::ResRef(self.grass_texture),
504 );
505 upsert_field(
506 &mut root,
507 "Grass_Density",
508 GffValue::Single(self.grass_density),
509 );
510 upsert_field(
511 &mut root,
512 "Grass_QuadSize",
513 GffValue::Single(self.grass_size),
514 );
515 upsert_field(
516 &mut root,
517 "Grass_Prob_LL",
518 GffValue::Single(self.grass_prob_ll),
519 );
520 upsert_field(
521 &mut root,
522 "Grass_Prob_LR",
523 GffValue::Single(self.grass_prob_lr),
524 );
525 upsert_field(
526 &mut root,
527 "Grass_Prob_UL",
528 GffValue::Single(self.grass_prob_ul),
529 );
530 upsert_field(
531 &mut root,
532 "Grass_Prob_UR",
533 GffValue::Single(self.grass_prob_ur),
534 );
535 upsert_field(
536 &mut root,
537 "SunFogOn",
538 GffValue::UInt8(u8::from(self.fog_enabled)),
539 );
540 upsert_field(&mut root, "SunFogNear", GffValue::Single(self.fog_near));
541 upsert_field(&mut root, "SunFogFar", GffValue::Single(self.fog_far));
542 upsert_field(
543 &mut root,
544 "SunShadows",
545 GffValue::UInt8(u8::from(self.shadows)),
546 );
547 upsert_field(
548 &mut root,
549 "ShadowOpacity",
550 GffValue::UInt8(self.shadow_opacity),
551 );
552 upsert_field(&mut root, "WindPower", GffValue::Int32(self.wind_power));
553 upsert_field(
554 &mut root,
555 "Unescapable",
556 GffValue::UInt8(u8::from(self.unescapable)),
557 );
558 upsert_field(
559 &mut root,
560 "DisableTransit",
561 GffValue::UInt8(u8::from(self.disable_transit)),
562 );
563 upsert_field(
564 &mut root,
565 "StealthXPEnabled",
566 GffValue::UInt8(u8::from(self.stealth_xp)),
567 );
568 upsert_field(
569 &mut root,
570 "StealthXPLoss",
571 GffValue::UInt32(self.stealth_xp_loss),
572 );
573 upsert_field(
574 &mut root,
575 "StealthXPMax",
576 GffValue::UInt32(self.stealth_xp_max),
577 );
578 upsert_field(
579 &mut root,
580 "StealthXPCurrent",
581 GffValue::UInt32(self.stealth_xp_current),
582 );
583 upsert_field(&mut root, "OnEnter", GffValue::ResRef(self.on_enter));
584 upsert_field(&mut root, "OnExit", GffValue::ResRef(self.on_exit));
585 upsert_field(
586 &mut root,
587 "OnHeartbeat",
588 GffValue::ResRef(self.on_heartbeat),
589 );
590 upsert_field(
591 &mut root,
592 "OnUserDefined",
593 GffValue::ResRef(self.on_user_defined),
594 );
595 upsert_field(&mut root, "Flags", GffValue::UInt32(self.flags));
596 upsert_field(
597 &mut root,
598 "LoadScreenID",
599 GffValue::UInt16(self.loadscreen_id),
600 );
601 upsert_field(&mut root, "ChanceRain", GffValue::Int32(self.chance_rain));
602 upsert_field(&mut root, "ChanceSnow", GffValue::Int32(self.chance_snow));
603 upsert_field(
604 &mut root,
605 "ChanceLightning",
606 GffValue::Int32(self.chance_lightning),
607 );
608 upsert_field(&mut root, "ChanceFog", GffValue::Int32(self.chance_fog));
609 upsert_field(
610 &mut root,
611 "ModSpotCheck",
612 GffValue::Int32(self.mod_spot_check),
613 );
614 upsert_field(
615 &mut root,
616 "ModListenCheck",
617 GffValue::Int32(self.mod_listen_check),
618 );
619 upsert_field(
620 &mut root,
621 "MoonAmbientColor",
622 GffValue::UInt32(self.moon_ambient_color),
623 );
624 upsert_field(
625 &mut root,
626 "MoonDiffuseColor",
627 GffValue::UInt32(self.moon_diffuse_color),
628 );
629 upsert_field(
630 &mut root,
631 "MoonFogOn",
632 GffValue::UInt8(u8::from(self.moon_fog_enabled)),
633 );
634 upsert_field(
635 &mut root,
636 "MoonFogNear",
637 GffValue::Single(self.moon_fog_near),
638 );
639 upsert_field(&mut root, "MoonFogFar", GffValue::Single(self.moon_fog_far));
640 upsert_field(
641 &mut root,
642 "MoonFogColor",
643 GffValue::UInt32(self.moon_fog_color),
644 );
645 upsert_field(
646 &mut root,
647 "MoonShadows",
648 GffValue::UInt8(u8::from(self.moon_shadows)),
649 );
650 upsert_field(
651 &mut root,
652 "SunAmbientColor",
653 GffValue::UInt32(self.sun_ambient_color),
654 );
655 upsert_field(
656 &mut root,
657 "SunDiffuseColor",
658 GffValue::UInt32(self.sun_diffuse_color),
659 );
660 upsert_field(
661 &mut root,
662 "DynAmbientColor",
663 GffValue::UInt32(self.dynamic_ambient_color),
664 );
665 upsert_field(
666 &mut root,
667 "SunFogColor",
668 GffValue::UInt32(self.sun_fog_color),
669 );
670 upsert_field(
671 &mut root,
672 "Grass_Ambient",
673 GffValue::UInt32(self.grass_ambient_color),
674 );
675 upsert_field(
676 &mut root,
677 "Grass_Diffuse",
678 GffValue::UInt32(self.grass_diffuse_color),
679 );
680 upsert_field(
681 &mut root,
682 "Grass_Emissive",
683 GffValue::UInt32(self.grass_emissive_color),
684 );
685 upsert_field(
686 &mut root,
687 "DirtyARGBOne",
688 GffValue::Int32(self.dirty_argb_one),
689 );
690 upsert_field(
691 &mut root,
692 "DirtySizeOne",
693 GffValue::Int32(self.dirty_size_one),
694 );
695 upsert_field(
696 &mut root,
697 "DirtyFormulaOne",
698 GffValue::Int32(self.dirty_formula_one),
699 );
700 upsert_field(
701 &mut root,
702 "DirtyFuncOne",
703 GffValue::Int32(self.dirty_func_one),
704 );
705 upsert_field(
706 &mut root,
707 "DirtyARGBTwo",
708 GffValue::Int32(self.dirty_argb_two),
709 );
710 upsert_field(
711 &mut root,
712 "DirtySizeTwo",
713 GffValue::Int32(self.dirty_size_two),
714 );
715 upsert_field(
716 &mut root,
717 "DirtyFormulaTwo",
718 GffValue::Int32(self.dirty_formula_two),
719 );
720 upsert_field(
721 &mut root,
722 "DirtyFuncTwo",
723 GffValue::Int32(self.dirty_func_two),
724 );
725 upsert_field(
726 &mut root,
727 "DirtyARGBThree",
728 GffValue::Int32(self.dirty_argb_three),
729 );
730 upsert_field(
731 &mut root,
732 "DirtySizeThree",
733 GffValue::Int32(self.dirty_size_three),
734 );
735 upsert_field(
736 &mut root,
737 "DirtyFormulaThre",
738 GffValue::Int32(self.dirty_formula_three),
739 );
740 upsert_field(
741 &mut root,
742 "DirtyFuncThree",
743 GffValue::Int32(self.dirty_func_three),
744 );
745 upsert_field(
746 &mut root,
747 "IsNight",
748 GffValue::UInt8(u8::from(self.is_night)),
749 );
750 upsert_field(
751 &mut root,
752 "LightingScheme",
753 GffValue::UInt8(self.lighting_scheme),
754 );
755 upsert_field(
756 &mut root,
757 "DayNightCycle",
758 GffValue::UInt8(self.day_night_cycle),
759 );
760 upsert_field(&mut root, "NoRest", GffValue::UInt8(u8::from(self.no_rest)));
761 upsert_field(
762 &mut root,
763 "NoHangBack",
764 GffValue::UInt8(u8::from(self.no_hang_back)),
765 );
766 upsert_field(
767 &mut root,
768 "PlayerOnly",
769 GffValue::UInt8(u8::from(self.player_only)),
770 );
771 upsert_field(
772 &mut root,
773 "PlayerVsPlayer",
774 GffValue::UInt8(self.player_vs_player),
775 );
776 upsert_field(
777 &mut root,
778 "TransPending",
779 GffValue::UInt8(self.trans_pending),
780 );
781 upsert_field(
782 &mut root,
783 "TransPendNextID",
784 GffValue::UInt8(self.trans_pend_next_id),
785 );
786 upsert_field(
787 &mut root,
788 "TransPendCurrID",
789 GffValue::UInt8(self.trans_pend_curr_id),
790 );
791
792 let map_struct = self.map.to_struct();
793 upsert_field(&mut root, "Map", GffValue::Struct(Box::new(map_struct)));
794
795 let room_structs = self
796 .rooms
797 .iter()
798 .map(AreRoom::to_struct)
799 .collect::<Vec<GffStruct>>();
800 upsert_field(&mut root, "Rooms", GffValue::List(room_structs));
801 let expansion_structs = self
802 .expansion_list
803 .iter()
804 .map(AreExpansionEntry::to_struct)
805 .collect::<Vec<GffStruct>>();
806 upsert_field(
807 &mut root,
808 "Expansion_List",
809 GffValue::List(expansion_structs),
810 );
811
812 if let Some(mg) = &self.mini_game {
813 upsert_field(
814 &mut root,
815 "MiniGame",
816 GffValue::Struct(Box::new(mg.to_struct())),
817 );
818 }
819
820 Gff::new(*b"ARE ", root)
821 }
822}
823
824#[derive(Debug, Clone, PartialEq)]
826pub struct AreMap {
827 pub north_axis: i32,
829 pub map_zoom: i32,
831 pub map_res_x: i32,
833 pub map_point_1: [f32; 2],
835 pub map_point_2: [f32; 2],
837 pub world_point_1: [f32; 2],
839 pub world_point_2: [f32; 2],
841}
842
843impl Default for AreMap {
844 fn default() -> Self {
845 Self {
846 north_axis: 0,
847 map_zoom: 0,
848 map_res_x: 0,
849 map_point_1: [0.0, 0.0],
850 map_point_2: [0.0, 0.0],
851 world_point_1: [0.0, 0.0],
852 world_point_2: [0.0, 0.0],
853 }
854 }
855}
856
857impl AreMap {
858 fn from_struct(structure: &GffStruct) -> Self {
859 Self {
860 north_axis: get_i32(structure, "NorthAxis").unwrap_or(0),
861 map_zoom: get_i32(structure, "MapZoom").unwrap_or(0),
862 map_res_x: get_i32(structure, "MapResX").unwrap_or(0),
863 map_point_1: [
864 get_map_point_f32(structure, "MapPt1X").unwrap_or(0.0),
865 get_map_point_f32(structure, "MapPt1Y").unwrap_or(0.0),
866 ],
867 map_point_2: [
868 get_map_point_f32(structure, "MapPt2X").unwrap_or(0.0),
869 get_map_point_f32(structure, "MapPt2Y").unwrap_or(0.0),
870 ],
871 world_point_1: [
872 get_f32(structure, "WorldPt1X").unwrap_or(0.0),
873 get_f32(structure, "WorldPt1Y").unwrap_or(0.0),
874 ],
875 world_point_2: [
876 get_f32(structure, "WorldPt2X").unwrap_or(0.0),
877 get_f32(structure, "WorldPt2Y").unwrap_or(0.0),
878 ],
879 }
880 }
881
882 fn to_struct(&self) -> GffStruct {
883 let mut structure = GffStruct::new(0);
884 upsert_field(
885 &mut structure,
886 "NorthAxis",
887 GffValue::Int32(self.north_axis),
888 );
889 upsert_field(&mut structure, "MapZoom", GffValue::Int32(self.map_zoom));
890 upsert_field(&mut structure, "MapResX", GffValue::Int32(self.map_res_x));
891 upsert_field(
892 &mut structure,
893 "MapPt1X",
894 GffValue::Single(self.map_point_1[0]),
895 );
896 upsert_field(
897 &mut structure,
898 "MapPt1Y",
899 GffValue::Single(self.map_point_1[1]),
900 );
901 upsert_field(
902 &mut structure,
903 "MapPt2X",
904 GffValue::Single(self.map_point_2[0]),
905 );
906 upsert_field(
907 &mut structure,
908 "MapPt2Y",
909 GffValue::Single(self.map_point_2[1]),
910 );
911 upsert_field(
912 &mut structure,
913 "WorldPt1X",
914 GffValue::Single(self.world_point_1[0]),
915 );
916 upsert_field(
917 &mut structure,
918 "WorldPt1Y",
919 GffValue::Single(self.world_point_1[1]),
920 );
921 upsert_field(
922 &mut structure,
923 "WorldPt2X",
924 GffValue::Single(self.world_point_2[0]),
925 );
926 upsert_field(
927 &mut structure,
928 "WorldPt2Y",
929 GffValue::Single(self.world_point_2[1]),
930 );
931 structure
932 }
933}
934
935#[derive(Debug, Clone, PartialEq)]
937pub struct AreRoom {
938 pub room_name: String,
940 pub ambient_scale: f32,
942 pub env_audio: i32,
944 pub force_rating: i32,
946 pub disable_weather: bool,
948 pub part_sounds: Vec<ArePartSound>,
950}
951
952impl AreRoom {
953 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
954 let part_sounds = match structure.field("PartSounds") {
955 Some(GffValue::List(part_sound_structs)) => part_sound_structs
956 .iter()
957 .map(ArePartSound::from_struct)
958 .collect::<Result<Vec<_>, _>>()?,
959 Some(_) => {
960 return Err(AreError::TypeMismatch {
961 field: "Rooms[].PartSounds",
962 expected: "List",
963 });
964 }
965 None => Vec::new(),
966 };
967
968 Ok(Self {
969 room_name: get_string(structure, "RoomName").unwrap_or_default(),
970 ambient_scale: get_f32(structure, "AmbientScale").unwrap_or(0.0),
971 env_audio: get_i32(structure, "EnvAudio").unwrap_or(0),
972 force_rating: get_i32(structure, "ForceRating").unwrap_or(0),
973 disable_weather: get_bool(structure, "DisableWeather").unwrap_or(false),
974 part_sounds,
975 })
976 }
977
978 fn to_struct(&self) -> GffStruct {
979 let mut structure = GffStruct::new(0);
980 upsert_field(
981 &mut structure,
982 "RoomName",
983 GffValue::String(self.room_name.clone()),
984 );
985 upsert_field(
986 &mut structure,
987 "AmbientScale",
988 GffValue::Single(self.ambient_scale),
989 );
990 upsert_field(&mut structure, "EnvAudio", GffValue::Int32(self.env_audio));
991 upsert_field(
992 &mut structure,
993 "ForceRating",
994 GffValue::Int32(self.force_rating),
995 );
996 upsert_field(
997 &mut structure,
998 "DisableWeather",
999 GffValue::UInt8(u8::from(self.disable_weather)),
1000 );
1001 let part_sound_structs = self
1002 .part_sounds
1003 .iter()
1004 .map(ArePartSound::to_struct)
1005 .collect::<Vec<GffStruct>>();
1006 upsert_field(
1007 &mut structure,
1008 "PartSounds",
1009 GffValue::List(part_sound_structs),
1010 );
1011 structure
1012 }
1013}
1014
1015#[derive(Debug, Clone, PartialEq)]
1017pub struct AreExpansionEntry {
1018 pub expansion_name: GffLocalizedString,
1020 pub expansion_id: i32,
1022}
1023
1024impl AreExpansionEntry {
1025 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1026 let expansion_name = match structure.field("Expansion_Name") {
1027 Some(GffValue::LocalizedString(value)) => value.clone(),
1028 Some(_) => {
1029 return Err(AreError::TypeMismatch {
1030 field: "Expansion_List[].Expansion_Name",
1031 expected: "LocalizedString",
1032 });
1033 }
1034 None => GffLocalizedString::new(StrRef::invalid()),
1035 };
1036
1037 Ok(Self {
1038 expansion_name,
1039 expansion_id: get_i32(structure, "Expansion_ID").unwrap_or(0),
1040 })
1041 }
1042
1043 fn to_struct(&self) -> GffStruct {
1044 let mut structure = GffStruct::new(0);
1045 upsert_field(
1046 &mut structure,
1047 "Expansion_Name",
1048 GffValue::LocalizedString(self.expansion_name.clone()),
1049 );
1050 upsert_field(
1051 &mut structure,
1052 "Expansion_ID",
1053 GffValue::Int32(self.expansion_id),
1054 );
1055 structure
1056 }
1057}
1058
1059#[derive(Debug, Clone, PartialEq)]
1061pub struct ArePartSound {
1062 pub looping: bool,
1064 pub model_part: String,
1066 pub omen_event: String,
1068 pub sound: ResRef,
1070}
1071
1072impl ArePartSound {
1073 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1074 let looping = match structure.field("Looping") {
1075 Some(GffValue::UInt8(value)) => *value != 0,
1076 Some(GffValue::Int8(value)) => *value != 0,
1077 Some(GffValue::UInt16(value)) => *value != 0,
1078 Some(GffValue::Int16(value)) => *value != 0,
1079 Some(GffValue::UInt32(value)) => *value != 0,
1080 Some(GffValue::Int32(value)) => *value != 0,
1081 Some(_) => {
1082 return Err(AreError::TypeMismatch {
1083 field: "Rooms[].PartSounds[].Looping",
1084 expected: "numeric bool",
1085 });
1086 }
1087 None => false,
1088 };
1089
1090 Ok(Self {
1091 looping,
1092 model_part: get_string(structure, "ModelPart").unwrap_or_default(),
1093 omen_event: get_string(structure, "OmenEvent").unwrap_or_default(),
1094 sound: get_resref(structure, "Sound").unwrap_or_default(),
1095 })
1096 }
1097
1098 fn to_struct(&self) -> GffStruct {
1099 let mut structure = GffStruct::new(0);
1100 upsert_field(
1101 &mut structure,
1102 "Looping",
1103 GffValue::UInt8(u8::from(self.looping)),
1104 );
1105 upsert_field(
1106 &mut structure,
1107 "ModelPart",
1108 GffValue::String(self.model_part.clone()),
1109 );
1110 upsert_field(
1111 &mut structure,
1112 "OmenEvent",
1113 GffValue::String(self.omen_event.clone()),
1114 );
1115 upsert_field(&mut structure, "Sound", GffValue::ResRef(self.sound));
1116 structure
1117 }
1118}
1119
1120#[derive(Debug, Error)]
1122pub enum AreError {
1123 #[error("unsupported ARE file type: {0:?}")]
1125 UnsupportedFileType([u8; 4]),
1126 #[error("ARE field `{field}` has incompatible type (expected {expected})")]
1128 TypeMismatch {
1129 field: &'static str,
1131 expected: &'static str,
1133 },
1134 #[error(transparent)]
1136 Gff(#[from] GffBinaryError),
1137}
1138
1139#[cfg_attr(
1141 feature = "tracing",
1142 tracing::instrument(level = "debug", skip(reader))
1143)]
1144pub fn read_are<R: Read>(reader: &mut R) -> Result<Are, AreError> {
1145 let gff = read_gff(reader)?;
1146 Are::from_gff(&gff)
1147}
1148
1149#[cfg_attr(
1151 feature = "tracing",
1152 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
1153)]
1154pub fn read_are_from_bytes(bytes: &[u8]) -> Result<Are, AreError> {
1155 let gff = read_gff_from_bytes(bytes)?;
1156 Are::from_gff(&gff)
1157}
1158
1159#[cfg_attr(
1161 feature = "tracing",
1162 tracing::instrument(level = "debug", skip(writer, are))
1163)]
1164pub fn write_are<W: Write>(writer: &mut W, are: &Are) -> Result<(), AreError> {
1165 let gff = are.to_gff();
1166 write_gff(writer, &gff)?;
1167 Ok(())
1168}
1169
1170#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(are)))]
1172pub fn write_are_to_vec(are: &Are) -> Result<Vec<u8>, AreError> {
1173 let mut cursor = Cursor::new(Vec::new());
1174 write_are(&mut cursor, are)?;
1175 Ok(cursor.into_inner())
1176}
1177
1178fn get_map_point_f32(structure: &GffStruct, label: &str) -> Option<f32> {
1179 match structure.field(label) {
1180 Some(GffValue::Single(value)) => Some(*value),
1181 #[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
1184 Some(GffValue::Double(value)) => Some(*value as f32),
1185 #[allow(clippy::cast_precision_loss, clippy::as_conversions)]
1186 Some(GffValue::Int32(value)) => Some(*value as f32),
1187 #[allow(clippy::cast_precision_loss, clippy::as_conversions)]
1188 Some(GffValue::UInt32(value)) => Some(*value as f32),
1189 Some(GffValue::Int16(value)) => Some(f32::from(*value)),
1190 Some(GffValue::UInt16(value)) => Some(f32::from(*value)),
1191 Some(GffValue::Int8(value)) => Some(f32::from(*value)),
1192 Some(GffValue::UInt8(value)) => Some(f32::from(*value)),
1193 _ => None,
1194 }
1195}
1196
1197static ROOMS_CHILDREN: &[FieldSchema] = &[
1199 FieldSchema {
1200 label: "PartSounds",
1201 expected_type: GffType::List,
1202 life: FieldLife::Live,
1203 required: false,
1204 absent: AbsentDefault::Unverified,
1205 children: None,
1206 constraint: None,
1207 },
1208 FieldSchema {
1209 label: "RoomName",
1210 expected_type: GffType::String,
1211 life: FieldLife::Live,
1212 required: false,
1213 absent: AbsentDefault::Unverified,
1214 children: None,
1215 constraint: None,
1216 },
1217 FieldSchema {
1218 label: "EnvAudio",
1219 expected_type: GffType::Int32,
1220 life: FieldLife::Live,
1221 required: false,
1222 absent: AbsentDefault::Unverified,
1223 children: None,
1224 constraint: None,
1225 },
1226 FieldSchema {
1227 label: "AmbientScale",
1228 expected_type: GffType::Single,
1229 life: FieldLife::Live,
1230 required: false,
1231 absent: AbsentDefault::Unverified,
1232 children: None,
1233 constraint: None,
1234 },
1235 FieldSchema {
1236 label: "ForceRating",
1237 expected_type: GffType::Int32,
1238 life: FieldLife::Live,
1239 required: false,
1240 absent: AbsentDefault::Unverified,
1241 children: None,
1242 constraint: None,
1243 },
1244 FieldSchema {
1245 label: "DisableWeather",
1246 expected_type: GffType::UInt8,
1247 life: FieldLife::Live,
1248 required: false,
1249 absent: AbsentDefault::Unverified,
1250 children: None,
1251 constraint: None,
1252 },
1253];
1254
1255static MAP_CHILDREN: &[FieldSchema] = &[
1257 FieldSchema {
1258 label: "MapResX",
1259 expected_type: GffType::Int32,
1260 life: FieldLife::Live,
1261 required: false,
1262 absent: AbsentDefault::Unverified,
1263 children: None,
1264 constraint: None,
1265 },
1266 FieldSchema {
1267 label: "NorthAxis",
1268 expected_type: GffType::Int32,
1269 life: FieldLife::Live,
1270 required: false,
1271 absent: AbsentDefault::Unverified,
1272 children: None,
1273 constraint: None,
1274 },
1275 FieldSchema {
1276 label: "MapZoom",
1277 expected_type: GffType::Int32,
1278 life: FieldLife::Live,
1279 required: false,
1280 absent: AbsentDefault::Unverified,
1281 children: None,
1282 constraint: None,
1283 },
1284 FieldSchema {
1285 label: "MapPt1X",
1286 expected_type: GffType::Single,
1287 life: FieldLife::Live,
1288 required: false,
1289 absent: AbsentDefault::Unverified,
1290 children: None,
1291 constraint: None,
1292 },
1293 FieldSchema {
1294 label: "MapPt1Y",
1295 expected_type: GffType::Single,
1296 life: FieldLife::Live,
1297 required: false,
1298 absent: AbsentDefault::Unverified,
1299 children: None,
1300 constraint: None,
1301 },
1302 FieldSchema {
1303 label: "MapPt2X",
1304 expected_type: GffType::Single,
1305 life: FieldLife::Live,
1306 required: false,
1307 absent: AbsentDefault::Unverified,
1308 children: None,
1309 constraint: None,
1310 },
1311 FieldSchema {
1312 label: "MapPt2Y",
1313 expected_type: GffType::Single,
1314 life: FieldLife::Live,
1315 required: false,
1316 absent: AbsentDefault::Unverified,
1317 children: None,
1318 constraint: None,
1319 },
1320 FieldSchema {
1321 label: "WorldPt1X",
1322 expected_type: GffType::Single,
1323 life: FieldLife::Live,
1324 required: false,
1325 absent: AbsentDefault::Unverified,
1326 children: None,
1327 constraint: None,
1328 },
1329 FieldSchema {
1330 label: "WorldPt1Y",
1331 expected_type: GffType::Single,
1332 life: FieldLife::Live,
1333 required: false,
1334 absent: AbsentDefault::Unverified,
1335 children: None,
1336 constraint: None,
1337 },
1338 FieldSchema {
1339 label: "WorldPt2X",
1340 expected_type: GffType::Single,
1341 life: FieldLife::Live,
1342 required: false,
1343 absent: AbsentDefault::Unverified,
1344 children: None,
1345 constraint: None,
1346 },
1347 FieldSchema {
1348 label: "WorldPt2Y",
1349 expected_type: GffType::Single,
1350 life: FieldLife::Live,
1351 required: false,
1352 absent: AbsentDefault::Unverified,
1353 children: None,
1354 constraint: None,
1355 },
1356];
1357
1358static EXPANSION_LIST_CHILDREN: &[FieldSchema] = &[
1360 FieldSchema {
1361 label: "Expansion_Name",
1362 expected_type: GffType::LocalizedString,
1363 life: FieldLife::Live,
1364 required: false,
1365 absent: AbsentDefault::Unverified,
1366 children: None,
1367 constraint: None,
1368 },
1369 FieldSchema {
1370 label: "Expansion_ID",
1371 expected_type: GffType::Int32,
1372 life: FieldLife::Live,
1373 required: false,
1374 absent: AbsentDefault::Unverified,
1375 children: None,
1376 constraint: None,
1377 },
1378];
1379
1380impl GffSchema for Are {
1381 fn schema() -> &'static [FieldSchema] {
1382 static SCHEMA: &[FieldSchema] = &[
1383 FieldSchema {
1385 label: "ID",
1386 expected_type: GffType::Int32,
1387 life: FieldLife::Live,
1388 required: false,
1389 absent: AbsentDefault::Unverified,
1390 children: None,
1391 constraint: None,
1392 },
1393 FieldSchema {
1394 label: "Creator_ID",
1395 expected_type: GffType::Int32,
1396 life: FieldLife::Live,
1397 required: false,
1398 absent: AbsentDefault::Unverified,
1399 children: None,
1400 constraint: None,
1401 },
1402 FieldSchema {
1403 label: "Version",
1404 expected_type: GffType::UInt32,
1405 life: FieldLife::Live,
1406 required: false,
1407 absent: AbsentDefault::Unverified,
1408 children: None,
1409 constraint: None,
1410 },
1411 FieldSchema {
1412 label: "Comments",
1413 expected_type: GffType::String,
1414 life: FieldLife::Live,
1415 required: false,
1416 absent: AbsentDefault::Unverified,
1417 children: None,
1418 constraint: None,
1419 },
1420 FieldSchema {
1421 label: "Name",
1422 expected_type: GffType::LocalizedString,
1423 life: FieldLife::Live,
1424 required: false,
1425 absent: AbsentDefault::Unverified,
1426 children: None,
1427 constraint: None,
1428 },
1429 FieldSchema {
1430 label: "Tag",
1431 expected_type: GffType::String,
1432 life: FieldLife::Live,
1433 required: false,
1434 absent: AbsentDefault::Unverified,
1435 children: None,
1436 constraint: None,
1437 },
1438 FieldSchema {
1440 label: "OnHeartbeat",
1441 expected_type: GffType::ResRef,
1442 life: FieldLife::Live,
1443 required: false,
1444 absent: AbsentDefault::Unverified,
1445 children: None,
1446 constraint: None,
1447 },
1448 FieldSchema {
1449 label: "OnUserDefined",
1450 expected_type: GffType::ResRef,
1451 life: FieldLife::Live,
1452 required: false,
1453 absent: AbsentDefault::Unverified,
1454 children: None,
1455 constraint: None,
1456 },
1457 FieldSchema {
1458 label: "OnEnter",
1459 expected_type: GffType::ResRef,
1460 life: FieldLife::Live,
1461 required: false,
1462 absent: AbsentDefault::Unverified,
1463 children: None,
1464 constraint: None,
1465 },
1466 FieldSchema {
1467 label: "OnExit",
1468 expected_type: GffType::ResRef,
1469 life: FieldLife::Live,
1470 required: false,
1471 absent: AbsentDefault::Unverified,
1472 children: None,
1473 constraint: None,
1474 },
1475 FieldSchema {
1477 label: "Flags",
1478 expected_type: GffType::UInt32,
1479 life: FieldLife::Live,
1480 required: false,
1481 absent: AbsentDefault::Unverified,
1482 children: None,
1483 constraint: None,
1484 },
1485 FieldSchema {
1486 label: "CameraStyle",
1487 expected_type: GffType::Int32,
1488 life: FieldLife::Live,
1489 required: false,
1490 absent: AbsentDefault::Unverified,
1491 children: None,
1492 constraint: None,
1493 },
1494 FieldSchema {
1495 label: "DefaultEnvMap",
1496 expected_type: GffType::ResRef,
1497 life: FieldLife::Live,
1498 required: false,
1499 absent: AbsentDefault::Unverified,
1500 children: None,
1501 constraint: None,
1502 },
1503 FieldSchema {
1504 label: "Unescapable",
1505 expected_type: GffType::UInt8,
1506 life: FieldLife::Live,
1507 required: false,
1508 absent: AbsentDefault::Unverified,
1509 children: None,
1510 constraint: None,
1511 },
1512 FieldSchema {
1513 label: "RestrictMode",
1514 expected_type: GffType::UInt8,
1515 life: FieldLife::Live,
1516 required: false,
1517 absent: AbsentDefault::Unverified,
1518 children: None,
1519 constraint: None,
1520 },
1521 FieldSchema {
1523 label: "ChanceRain",
1524 expected_type: GffType::Int32,
1525 life: FieldLife::Live,
1526 required: false,
1527 absent: AbsentDefault::Unverified,
1528 children: None,
1529 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1530 },
1531 FieldSchema {
1532 label: "ChanceSnow",
1533 expected_type: GffType::Int32,
1534 life: FieldLife::Live,
1535 required: false,
1536 absent: AbsentDefault::Unverified,
1537 children: None,
1538 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1539 },
1540 FieldSchema {
1541 label: "ChanceLightning",
1542 expected_type: GffType::Int32,
1543 life: FieldLife::Live,
1544 required: false,
1545 absent: AbsentDefault::Unverified,
1546 children: None,
1547 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1548 },
1549 FieldSchema {
1550 label: "WindPower",
1551 expected_type: GffType::Int32,
1552 life: FieldLife::Live,
1553 required: false,
1554 absent: AbsentDefault::Unverified,
1555 children: None,
1556 constraint: Some(FieldConstraint::RangeInt(0, 2)),
1557 },
1558 FieldSchema {
1559 label: "ChanceFog",
1560 expected_type: GffType::Int32,
1561 life: FieldLife::Live,
1562 required: false,
1563 absent: AbsentDefault::Unverified,
1564 children: None,
1565 constraint: None,
1566 },
1567 FieldSchema {
1569 label: "MoonAmbientColor",
1570 expected_type: GffType::UInt32,
1571 life: FieldLife::Live,
1572 required: false,
1573 absent: AbsentDefault::Unverified,
1574 children: None,
1575 constraint: None,
1576 },
1577 FieldSchema {
1578 label: "MoonDiffuseColor",
1579 expected_type: GffType::UInt32,
1580 life: FieldLife::Live,
1581 required: false,
1582 absent: AbsentDefault::Unverified,
1583 children: None,
1584 constraint: None,
1585 },
1586 FieldSchema {
1587 label: "MoonFogColor",
1588 expected_type: GffType::UInt32,
1589 life: FieldLife::Live,
1590 required: false,
1591 absent: AbsentDefault::Unverified,
1592 children: None,
1593 constraint: None,
1594 },
1595 FieldSchema {
1596 label: "SunAmbientColor",
1597 expected_type: GffType::UInt32,
1598 life: FieldLife::Live,
1599 required: false,
1600 absent: AbsentDefault::Unverified,
1601 children: None,
1602 constraint: None,
1603 },
1604 FieldSchema {
1605 label: "SunDiffuseColor",
1606 expected_type: GffType::UInt32,
1607 life: FieldLife::Live,
1608 required: false,
1609 absent: AbsentDefault::Unverified,
1610 children: None,
1611 constraint: None,
1612 },
1613 FieldSchema {
1614 label: "SunFogColor",
1615 expected_type: GffType::UInt32,
1616 life: FieldLife::Live,
1617 required: false,
1618 absent: AbsentDefault::Unverified,
1619 children: None,
1620 constraint: None,
1621 },
1622 FieldSchema {
1623 label: "DynAmbientColor",
1624 expected_type: GffType::UInt32,
1625 life: FieldLife::Live,
1626 required: false,
1627 absent: AbsentDefault::Unverified,
1628 children: None,
1629 constraint: None,
1630 },
1631 FieldSchema {
1632 label: "MoonFogNear",
1633 expected_type: GffType::Single,
1634 life: FieldLife::Live,
1635 required: false,
1636 absent: AbsentDefault::Unverified,
1637 children: None,
1638 constraint: None,
1639 },
1640 FieldSchema {
1641 label: "MoonFogFar",
1642 expected_type: GffType::Single,
1643 life: FieldLife::Live,
1644 required: false,
1645 absent: AbsentDefault::Unverified,
1646 children: None,
1647 constraint: None,
1648 },
1649 FieldSchema {
1650 label: "SunFogNear",
1651 expected_type: GffType::Single,
1652 life: FieldLife::Live,
1653 required: false,
1654 absent: AbsentDefault::Unverified,
1655 children: None,
1656 constraint: None,
1657 },
1658 FieldSchema {
1659 label: "SunFogFar",
1660 expected_type: GffType::Single,
1661 life: FieldLife::Live,
1662 required: false,
1663 absent: AbsentDefault::Unverified,
1664 children: None,
1665 constraint: None,
1666 },
1667 FieldSchema {
1668 label: "MoonFogOn",
1669 expected_type: GffType::UInt8,
1670 life: FieldLife::Live,
1671 required: false,
1672 absent: AbsentDefault::Unverified,
1673 children: None,
1674 constraint: None,
1675 },
1676 FieldSchema {
1677 label: "SunFogOn",
1678 expected_type: GffType::UInt8,
1679 life: FieldLife::Live,
1680 required: false,
1681 absent: AbsentDefault::Unverified,
1682 children: None,
1683 constraint: None,
1684 },
1685 FieldSchema {
1686 label: "MoonShadows",
1687 expected_type: GffType::UInt8,
1688 life: FieldLife::Live,
1689 required: false,
1690 absent: AbsentDefault::Unverified,
1691 children: None,
1692 constraint: None,
1693 },
1694 FieldSchema {
1695 label: "SunShadows",
1696 expected_type: GffType::UInt8,
1697 life: FieldLife::Live,
1698 required: false,
1699 absent: AbsentDefault::Unverified,
1700 children: None,
1701 constraint: None,
1702 },
1703 FieldSchema {
1704 label: "DayNightCycle",
1705 expected_type: GffType::UInt8,
1706 life: FieldLife::Live,
1707 required: false,
1708 absent: AbsentDefault::Unverified,
1709 children: None,
1710 constraint: None,
1711 },
1712 FieldSchema {
1713 label: "IsNight",
1714 expected_type: GffType::UInt8,
1715 life: FieldLife::Live,
1716 required: false,
1717 absent: AbsentDefault::Unverified,
1718 children: None,
1719 constraint: None,
1720 },
1721 FieldSchema {
1722 label: "ShadowOpacity",
1723 expected_type: GffType::UInt8,
1724 life: FieldLife::Live,
1725 required: false,
1726 absent: AbsentDefault::Unverified,
1727 children: None,
1728 constraint: None,
1729 },
1730 FieldSchema {
1731 label: "LightingScheme",
1732 expected_type: GffType::UInt8,
1733 life: FieldLife::Live,
1734 required: false,
1735 absent: AbsentDefault::Unverified,
1736 children: None,
1737 constraint: None,
1738 },
1739 FieldSchema {
1740 label: "NoRest",
1741 expected_type: GffType::UInt8,
1742 life: FieldLife::Live,
1743 required: false,
1744 absent: AbsentDefault::Unverified,
1745 children: None,
1746 constraint: None,
1747 },
1748 FieldSchema {
1750 label: "ModSpotCheck",
1751 expected_type: GffType::Int32,
1752 life: FieldLife::Live,
1753 required: false,
1754 absent: AbsentDefault::Unverified,
1755 children: None,
1756 constraint: None,
1757 },
1758 FieldSchema {
1759 label: "ModListenCheck",
1760 expected_type: GffType::Int32,
1761 life: FieldLife::Live,
1762 required: false,
1763 absent: AbsentDefault::Unverified,
1764 children: None,
1765 constraint: None,
1766 },
1767 FieldSchema {
1769 label: "Grass_Diffuse",
1770 expected_type: GffType::UInt32,
1771 life: FieldLife::Live,
1772 required: false,
1773 absent: AbsentDefault::Unverified,
1774 children: None,
1775 constraint: None,
1776 },
1777 FieldSchema {
1778 label: "Grass_Ambient",
1779 expected_type: GffType::UInt32,
1780 life: FieldLife::Live,
1781 required: false,
1782 absent: AbsentDefault::Unverified,
1783 children: None,
1784 constraint: None,
1785 },
1786 FieldSchema {
1787 label: "Grass_Density",
1788 expected_type: GffType::Single,
1789 life: FieldLife::Live,
1790 required: false,
1791 absent: AbsentDefault::Unverified,
1792 children: None,
1793 constraint: None,
1794 },
1795 FieldSchema {
1796 label: "Grass_QuadSize",
1797 expected_type: GffType::Single,
1798 life: FieldLife::Live,
1799 required: false,
1800 absent: AbsentDefault::Unverified,
1801 children: None,
1802 constraint: None,
1803 },
1804 FieldSchema {
1805 label: "Grass_TexName",
1806 expected_type: GffType::ResRef,
1807 life: FieldLife::Live,
1808 required: false,
1809 absent: AbsentDefault::Unverified,
1810 children: None,
1811 constraint: None,
1812 },
1813 FieldSchema {
1814 label: "Grass_Prob_LL",
1815 expected_type: GffType::Single,
1816 life: FieldLife::Live,
1817 required: false,
1818 absent: AbsentDefault::Unverified,
1819 children: None,
1820 constraint: None,
1821 },
1822 FieldSchema {
1823 label: "Grass_Prob_LR",
1824 expected_type: GffType::Single,
1825 life: FieldLife::Live,
1826 required: false,
1827 absent: AbsentDefault::Unverified,
1828 children: None,
1829 constraint: None,
1830 },
1831 FieldSchema {
1832 label: "Grass_Prob_UL",
1833 expected_type: GffType::Single,
1834 life: FieldLife::Live,
1835 required: false,
1836 absent: AbsentDefault::Unverified,
1837 children: None,
1838 constraint: None,
1839 },
1840 FieldSchema {
1841 label: "Grass_Prob_UR",
1842 expected_type: GffType::Single,
1843 life: FieldLife::Live,
1844 required: false,
1845 absent: AbsentDefault::Unverified,
1846 children: None,
1847 constraint: None,
1848 },
1849 FieldSchema {
1850 label: "AlphaTest",
1851 expected_type: GffType::Single,
1852 life: FieldLife::Live,
1853 required: false,
1854 absent: AbsentDefault::Unverified,
1855 children: None,
1856 constraint: None,
1857 },
1858 FieldSchema {
1860 label: "StealthXPMax",
1861 expected_type: GffType::UInt32,
1862 life: FieldLife::Live,
1863 required: false,
1864 absent: AbsentDefault::Unverified,
1865 children: None,
1866 constraint: None,
1867 },
1868 FieldSchema {
1869 label: "StealthXPCurrent",
1870 expected_type: GffType::UInt32,
1871 life: FieldLife::Live,
1872 required: false,
1873 absent: AbsentDefault::Unverified,
1874 children: None,
1875 constraint: None,
1876 },
1877 FieldSchema {
1878 label: "StealthXPLoss",
1879 expected_type: GffType::UInt32,
1880 life: FieldLife::Live,
1881 required: false,
1882 absent: AbsentDefault::Unverified,
1883 children: None,
1884 constraint: None,
1885 },
1886 FieldSchema {
1887 label: "StealthXPEnabled",
1888 expected_type: GffType::UInt8,
1889 life: FieldLife::Live,
1890 required: false,
1891 absent: AbsentDefault::Unverified,
1892 children: None,
1893 constraint: None,
1894 },
1895 FieldSchema {
1896 label: "TransPending",
1897 expected_type: GffType::UInt8,
1898 life: FieldLife::Live,
1899 required: false,
1900 absent: AbsentDefault::Unverified,
1901 children: None,
1902 constraint: None,
1903 },
1904 FieldSchema {
1905 label: "TransPendNextID",
1906 expected_type: GffType::UInt8,
1907 life: FieldLife::Live,
1908 required: false,
1909 absent: AbsentDefault::Unverified,
1910 children: None,
1911 constraint: None,
1912 },
1913 FieldSchema {
1914 label: "TransPendCurrID",
1915 expected_type: GffType::UInt8,
1916 life: FieldLife::Live,
1917 required: false,
1918 absent: AbsentDefault::Unverified,
1919 children: None,
1920 constraint: None,
1921 },
1922 FieldSchema {
1924 label: "LoadScreenID",
1925 expected_type: GffType::UInt16,
1926 life: FieldLife::Live,
1927 required: false,
1928 absent: AbsentDefault::Unverified,
1929 children: None,
1930 constraint: None,
1931 },
1932 FieldSchema {
1934 label: "Rooms",
1935 expected_type: GffType::List,
1936 life: FieldLife::Live,
1937 required: false,
1938 absent: AbsentDefault::Unverified,
1939 children: Some(ROOMS_CHILDREN),
1940 constraint: None,
1941 },
1942 FieldSchema {
1943 label: "Expansion_List",
1944 expected_type: GffType::List,
1945 life: FieldLife::Live,
1946 required: false,
1947 absent: AbsentDefault::Unverified,
1948 children: Some(EXPANSION_LIST_CHILDREN),
1949 constraint: None,
1950 },
1951 FieldSchema {
1952 label: "Map",
1953 expected_type: GffType::Struct,
1954 life: FieldLife::Live,
1955 required: false,
1956 absent: AbsentDefault::Unverified,
1957 children: Some(MAP_CHILDREN),
1958 constraint: None,
1959 },
1960 FieldSchema {
1961 label: "MiniGame",
1962 expected_type: GffType::Struct,
1963 life: FieldLife::Live,
1964 required: false,
1965 absent: AbsentDefault::Unverified,
1966 children: Some(minigame::MINI_GAME_CHILDREN),
1967 constraint: None,
1968 },
1969 FieldSchema {
1971 label: "DisableTransit",
1972 expected_type: GffType::UInt8,
1973 life: FieldLife::Live,
1974 required: false,
1975 absent: AbsentDefault::Unverified,
1976 children: None,
1977 constraint: None,
1978 },
1979 FieldSchema {
1980 label: "NoHangBack",
1981 expected_type: GffType::UInt8,
1982 life: FieldLife::Live,
1983 required: false,
1984 absent: AbsentDefault::Unverified,
1985 children: None,
1986 constraint: None,
1987 },
1988 FieldSchema {
1989 label: "PlayerOnly",
1990 expected_type: GffType::UInt8,
1991 life: FieldLife::Live,
1992 required: false,
1993 absent: AbsentDefault::Unverified,
1994 children: None,
1995 constraint: None,
1996 },
1997 FieldSchema {
1998 label: "PlayerVsPlayer",
1999 expected_type: GffType::UInt8,
2000 life: FieldLife::Live,
2001 required: false,
2002 absent: AbsentDefault::Unverified,
2003 children: None,
2004 constraint: None,
2005 },
2006 FieldSchema {
2007 label: "Grass_Emissive",
2008 expected_type: GffType::UInt32,
2009 life: FieldLife::Live,
2010 required: false,
2011 absent: AbsentDefault::Unverified,
2012 children: None,
2013 constraint: None,
2014 },
2015 FieldSchema {
2017 label: "DirtyARGBOne",
2018 expected_type: GffType::Int32,
2019 life: FieldLife::Live,
2020 required: false,
2021 absent: AbsentDefault::Unverified,
2022 children: None,
2023 constraint: None,
2024 },
2025 FieldSchema {
2026 label: "DirtySizeOne",
2027 expected_type: GffType::Int32,
2028 life: FieldLife::Live,
2029 required: false,
2030 absent: AbsentDefault::Unverified,
2031 children: None,
2032 constraint: None,
2033 },
2034 FieldSchema {
2035 label: "DirtyFormulaOne",
2036 expected_type: GffType::Int32,
2037 life: FieldLife::Live,
2038 required: false,
2039 absent: AbsentDefault::Unverified,
2040 children: None,
2041 constraint: None,
2042 },
2043 FieldSchema {
2044 label: "DirtyFuncOne",
2045 expected_type: GffType::Int32,
2046 life: FieldLife::Live,
2047 required: false,
2048 absent: AbsentDefault::Unverified,
2049 children: None,
2050 constraint: None,
2051 },
2052 FieldSchema {
2053 label: "DirtyARGBTwo",
2054 expected_type: GffType::Int32,
2055 life: FieldLife::Live,
2056 required: false,
2057 absent: AbsentDefault::Unverified,
2058 children: None,
2059 constraint: None,
2060 },
2061 FieldSchema {
2062 label: "DirtySizeTwo",
2063 expected_type: GffType::Int32,
2064 life: FieldLife::Live,
2065 required: false,
2066 absent: AbsentDefault::Unverified,
2067 children: None,
2068 constraint: None,
2069 },
2070 FieldSchema {
2071 label: "DirtyFormulaTwo",
2072 expected_type: GffType::Int32,
2073 life: FieldLife::Live,
2074 required: false,
2075 absent: AbsentDefault::Unverified,
2076 children: None,
2077 constraint: None,
2078 },
2079 FieldSchema {
2080 label: "DirtyFuncTwo",
2081 expected_type: GffType::Int32,
2082 life: FieldLife::Live,
2083 required: false,
2084 absent: AbsentDefault::Unverified,
2085 children: None,
2086 constraint: None,
2087 },
2088 FieldSchema {
2089 label: "DirtyARGBThree",
2090 expected_type: GffType::Int32,
2091 life: FieldLife::Live,
2092 required: false,
2093 absent: AbsentDefault::Unverified,
2094 children: None,
2095 constraint: None,
2096 },
2097 FieldSchema {
2098 label: "DirtySizeThree",
2099 expected_type: GffType::Int32,
2100 life: FieldLife::Live,
2101 required: false,
2102 absent: AbsentDefault::Unverified,
2103 children: None,
2104 constraint: None,
2105 },
2106 FieldSchema {
2107 label: "DirtyFormulaThre",
2108 expected_type: GffType::Int32,
2109 life: FieldLife::Live,
2110 required: false,
2111 absent: AbsentDefault::Unverified,
2112 children: None,
2113 constraint: None,
2114 },
2115 FieldSchema {
2116 label: "DirtyFuncThree",
2117 expected_type: GffType::Int32,
2118 life: FieldLife::Live,
2119 required: false,
2120 absent: AbsentDefault::Unverified,
2121 children: None,
2122 constraint: None,
2123 },
2124 ];
2125 SCHEMA
2126 }
2127}
2128
2129#[cfg(test)]
2130mod tests {
2131 use super::*;
2132
2133 const TEST_ARE: &[u8] = include_bytes!(concat!(
2134 env!("CARGO_MANIFEST_DIR"),
2135 "/../../fixtures/test.are"
2136 ));
2137
2138 #[test]
2139 fn reads_core_are_fields_from_fixture() {
2140 let are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2141
2142 assert_eq!(are.unused_id, 0);
2143 assert_eq!(are.creator_id, 0);
2144 assert_eq!(are.tag, "Untitled");
2145 assert_eq!(are.name.string_ref.raw(), 75_101);
2146 assert_eq!(are.comment, "comments");
2147 assert_eq!(are.version, 88);
2148 assert_eq!(are.flags, 0);
2149 assert_eq!(are.mod_spot_check, 0);
2150 assert_eq!(are.mod_listen_check, 0);
2151 assert_eq!(are.camera_style, 1);
2152 assert_eq!(are.default_envmap, "defaultenvmap");
2153 assert_eq!(are.grass_texture, "grasstexture");
2154 assert!((are.grass_density - 1.0).abs() < f32::EPSILON);
2155 assert!((are.grass_size - 1.0).abs() < f32::EPSILON);
2156 assert!((are.grass_prob_ll - 0.25).abs() < f32::EPSILON);
2157 assert!((are.grass_prob_lr - 0.25).abs() < f32::EPSILON);
2158 assert!((are.grass_prob_ul - 0.25).abs() < f32::EPSILON);
2159 assert!((are.grass_prob_ur - 0.25).abs() < f32::EPSILON);
2160 assert_eq!(are.sun_ambient_color, 16_777_215);
2161 assert_eq!(are.sun_diffuse_color, 16_777_215);
2162 assert_eq!(are.dynamic_ambient_color, 16_777_215);
2163 assert_eq!(are.sun_fog_color, 16_777_215);
2164 assert_eq!(are.grass_ambient_color, 16_777_215);
2165 assert_eq!(are.grass_diffuse_color, 16_777_215);
2166 assert_eq!(are.grass_emissive_color, 16_777_215);
2167 assert!(are.fog_enabled);
2168 assert!((are.fog_near - 99.0).abs() < f32::EPSILON);
2169 assert!((are.fog_far - 100.0).abs() < f32::EPSILON);
2170 assert!(are.shadows);
2171 assert_eq!(are.shadow_opacity, 205);
2172 assert_eq!(are.wind_power, 1);
2173 assert!(are.unescapable);
2174 assert!(are.disable_transit);
2175 assert!(are.stealth_xp);
2176 assert_eq!(are.stealth_xp_loss, 25);
2177 assert_eq!(are.stealth_xp_max, 25);
2178 assert_eq!(are.on_enter, "k_on_enter");
2179 assert_eq!(are.on_exit, "onexit");
2180 assert_eq!(are.on_heartbeat, "onheartbeat");
2181 assert_eq!(are.on_user_defined, "onuserdefined");
2182 assert!((are.alpha_test - 0.2).abs() < f32::EPSILON);
2183 assert_eq!(are.chance_rain, 99);
2184 assert_eq!(are.chance_snow, 99);
2185 assert_eq!(are.chance_lightning, 99);
2186 assert_eq!(are.moon_ambient_color, 0);
2187 assert_eq!(are.moon_diffuse_color, 0);
2188 assert!(!are.moon_fog_enabled);
2189 assert!((are.moon_fog_near - 99.0).abs() < f32::EPSILON);
2190 assert!((are.moon_fog_far - 100.0).abs() < f32::EPSILON);
2191 assert_eq!(are.moon_fog_color, 0);
2192 assert!(!are.moon_shadows);
2193 assert_eq!(are.dirty_argb_one, 123);
2194 assert_eq!(are.dirty_size_one, 1);
2195 assert_eq!(are.dirty_formula_one, 1);
2196 assert_eq!(are.dirty_func_one, 1);
2197 assert_eq!(are.dirty_argb_two, 1234);
2198 assert_eq!(are.dirty_size_two, 1);
2199 assert_eq!(are.dirty_formula_two, 1);
2200 assert_eq!(are.dirty_func_two, 1);
2201 assert_eq!(are.dirty_argb_three, 12_345);
2202 assert_eq!(are.dirty_size_three, 1);
2203 assert_eq!(are.dirty_formula_three, 1);
2204 assert_eq!(are.dirty_func_three, 1);
2205 assert!(!are.is_night);
2206 assert_eq!(are.lighting_scheme, 0);
2207 assert_eq!(are.day_night_cycle, 0);
2208 assert!(!are.no_rest);
2209 assert!(!are.no_hang_back);
2210 assert!(!are.player_only);
2211 assert_eq!(are.player_vs_player, 3);
2212 assert_eq!(are.map.map_zoom, 1);
2213 assert_eq!(are.map.map_res_x, 18);
2214 assert_eq!(are.rooms.len(), 2);
2215 assert!(are.expansion_list.is_empty());
2216 assert_eq!(are.rooms[0].room_name, "002ebo");
2217 assert!(are.rooms[0].disable_weather);
2218 assert!(are.rooms[0].part_sounds.is_empty());
2219 }
2220
2221 #[test]
2222 fn all_fields_survive_typed_roundtrip() {
2223 let are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2224 let encoded = write_are_to_vec(&are).expect("encode must succeed");
2225 let reparsed = read_are_from_bytes(&encoded).expect("decode must succeed");
2226 assert_eq!(are, reparsed);
2227 }
2228
2229 #[test]
2230 fn typed_edits_roundtrip_through_gff_writer() {
2231 let mut are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2232 are.tag = "m01aa".into();
2233 are.on_enter = ResRef::new("k_on_newenter").expect("valid test resref");
2234 are.grass_texture = ResRef::new("new_grass").expect("valid test resref");
2235 are.fog_enabled = false;
2236 are.fog_near = 50.0;
2237 are.shadow_opacity = 180;
2238 are.stealth_xp_loss = 33;
2239 are.dirty_formula_three = 9;
2240 are.player_vs_player = 2;
2241 are.restrict_mode = 1;
2242 are.chance_fog = 42;
2243 are.stealth_xp_current = 10;
2244 are.trans_pending = 1;
2245 are.trans_pend_next_id = 3;
2246 are.trans_pend_curr_id = 2;
2247 are.map.map_zoom = 7;
2248 are.rooms[0].ambient_scale = 0.5;
2249 are.expansion_list.push(AreExpansionEntry {
2250 expansion_name: GffLocalizedString::new(StrRef::from_raw(321)),
2251 expansion_id: 42,
2252 });
2253 are.rooms[0].part_sounds.push(ArePartSound {
2254 looping: true,
2255 model_part: "ROOM_A".into(),
2256 omen_event: "ON_ENTER".into(),
2257 sound: ResRef::new("amb_rooma").expect("valid test resref"),
2258 });
2259
2260 let encoded = write_are_to_vec(&are).expect("encode");
2261 let reparsed = read_are_from_bytes(&encoded).expect("decode");
2262
2263 assert_eq!(reparsed.tag, "m01aa");
2264 assert_eq!(reparsed.on_enter, "k_on_newenter");
2265 assert_eq!(reparsed.grass_texture, "new_grass");
2266 assert!(!reparsed.fog_enabled);
2267 assert!((reparsed.fog_near - 50.0).abs() < f32::EPSILON);
2268 assert_eq!(reparsed.shadow_opacity, 180);
2269 assert_eq!(reparsed.stealth_xp_loss, 33);
2270 assert_eq!(reparsed.dirty_formula_three, 9);
2271 assert_eq!(reparsed.player_vs_player, 2);
2272 assert_eq!(reparsed.restrict_mode, 1);
2273 assert_eq!(reparsed.chance_fog, 42);
2274 assert_eq!(reparsed.stealth_xp_current, 10);
2275 assert_eq!(reparsed.trans_pending, 1);
2276 assert_eq!(reparsed.trans_pend_next_id, 3);
2277 assert_eq!(reparsed.trans_pend_curr_id, 2);
2278 assert_eq!(reparsed.map.map_zoom, 7);
2279 assert_eq!(reparsed.rooms[0].ambient_scale, 0.5);
2280 assert_eq!(reparsed.expansion_list.len(), 1);
2281 assert_eq!(reparsed.expansion_list[0].expansion_id, 42);
2282 assert_eq!(
2283 reparsed.expansion_list[0].expansion_name.string_ref.raw(),
2284 321
2285 );
2286 assert_eq!(reparsed.rooms[0].part_sounds.len(), 1);
2287 assert!(reparsed.rooms[0].part_sounds[0].looping);
2288 assert_eq!(reparsed.rooms[0].part_sounds[0].model_part, "ROOM_A");
2289 assert_eq!(reparsed.rooms[0].part_sounds[0].omen_event, "ON_ENTER");
2290 assert_eq!(reparsed.rooms[0].part_sounds[0].sound, "amb_rooma");
2291 assert_eq!(reparsed.rooms.len(), 2);
2292 }
2293
2294 #[test]
2295 fn rejects_non_are_file_type() {
2296 let gff = Gff::new(*b"DLG ", GffStruct::new(-1));
2297 let err = Are::from_gff(&gff).expect_err("must fail");
2298 assert!(matches!(err, AreError::UnsupportedFileType(file_type) if file_type == *b"DLG "));
2299 }
2300
2301 #[test]
2302 fn read_are_from_reader_matches_bytes_path() {
2303 let mut cursor = Cursor::new(TEST_ARE);
2304 let via_reader = read_are(&mut cursor).expect("reader parse");
2305 let via_bytes = read_are_from_bytes(TEST_ARE).expect("bytes parse");
2306 assert_eq!(via_reader.tag, via_bytes.tag);
2307 assert_eq!(via_reader.rooms.len(), via_bytes.rooms.len());
2308 }
2309
2310 #[test]
2311 fn type_mismatch_on_rooms_field_is_error() {
2312 let mut root = GffStruct::new(-1);
2313 root.push_field("Rooms", GffValue::UInt32(7));
2314 let gff = Gff::new(*b"ARE ", root);
2315 let err = Are::from_gff(&gff).expect_err("must fail");
2316 assert!(matches!(
2317 err,
2318 AreError::TypeMismatch {
2319 field: "Rooms",
2320 expected: "List"
2321 }
2322 ));
2323 }
2324
2325 #[test]
2326 fn map_points_accept_int_encoding_for_k1_parity() {
2327 let mut root = GffStruct::new(-1);
2328 let mut map = GffStruct::new(0);
2329 map.push_field("MapPt1X", GffValue::Int32(10));
2330 map.push_field("MapPt1Y", GffValue::Int32(20));
2331 map.push_field("MapPt2X", GffValue::UInt16(30));
2332 map.push_field("MapPt2Y", GffValue::UInt8(40));
2333 root.push_field("Map", GffValue::Struct(Box::new(map)));
2334 let gff = Gff::new(*b"ARE ", root);
2335
2336 let are = Are::from_gff(&gff).expect("must parse");
2337 assert_eq!(are.map.map_point_1, [10.0, 20.0]);
2338 assert_eq!(are.map.map_point_2, [30.0, 40.0]);
2339 }
2340
2341 #[test]
2342 fn type_mismatch_on_expansion_list_field_is_error() {
2343 let mut root = GffStruct::new(-1);
2344 root.push_field("Expansion_List", GffValue::UInt32(7));
2345 let gff = Gff::new(*b"ARE ", root);
2346 let err = Are::from_gff(&gff).expect_err("must fail");
2347 assert!(matches!(
2348 err,
2349 AreError::TypeMismatch {
2350 field: "Expansion_List",
2351 expected: "List"
2352 }
2353 ));
2354 }
2355
2356 #[test]
2357 fn type_mismatch_on_room_part_sounds_field_is_error() {
2358 let mut root = GffStruct::new(-1);
2359 let mut room = GffStruct::new(0);
2360 room.push_field("PartSounds", GffValue::UInt32(7));
2361 root.push_field("Rooms", GffValue::List(vec![room]));
2362 let gff = Gff::new(*b"ARE ", root);
2363 let err = Are::from_gff(&gff).expect_err("must fail");
2364 assert!(matches!(
2365 err,
2366 AreError::TypeMismatch {
2367 field: "Rooms[].PartSounds",
2368 expected: "List"
2369 }
2370 ));
2371 }
2372
2373 #[test]
2374 fn type_mismatch_on_part_sound_looping_field_is_error() {
2375 let mut root = GffStruct::new(-1);
2376 let mut room = GffStruct::new(0);
2377 let mut part_sound = GffStruct::new(0);
2378 part_sound.push_field("Looping", GffValue::String("yes".into()));
2379 room.push_field("PartSounds", GffValue::List(vec![part_sound]));
2380 root.push_field("Rooms", GffValue::List(vec![room]));
2381 let gff = Gff::new(*b"ARE ", root);
2382 let err = Are::from_gff(&gff).expect_err("must fail");
2383 assert!(matches!(
2384 err,
2385 AreError::TypeMismatch {
2386 field: "Rooms[].PartSounds[].Looping",
2387 expected: "numeric bool"
2388 }
2389 ));
2390 }
2391
2392 #[test]
2393 fn write_are_matches_direct_gff_writer() {
2394 let are = read_are_from_bytes(TEST_ARE).expect("fixture parse");
2395 let from_are = write_are_to_vec(&are).expect("are encode");
2396
2397 let gff = are.to_gff();
2398 let from_gff = rakata_formats::write_gff_to_vec(&gff).expect("gff encode");
2399 assert_eq!(from_are, from_gff);
2400 }
2401
2402 #[test]
2403 fn schema_field_count() {
2404 assert_eq!(Are::schema().len(), 81);
2405 }
2406
2407 #[test]
2408 fn schema_no_duplicate_labels() {
2409 let schema = Are::schema();
2410 let mut labels: Vec<&str> = schema.iter().map(|f| f.label).collect();
2411 labels.sort();
2412 let before = labels.len();
2413 labels.dedup();
2414 assert_eq!(before, labels.len(), "duplicate labels in ARE schema");
2415 }
2416
2417 #[test]
2418 fn schema_rooms_has_children() {
2419 let rooms = Are::schema()
2420 .iter()
2421 .find(|f| f.label == "Rooms")
2422 .expect("Rooms field must exist in schema");
2423 assert!(rooms.children.is_some());
2424 assert_eq!(
2425 rooms
2426 .children
2427 .expect("Rooms children must be present")
2428 .len(),
2429 6
2430 );
2431 }
2432
2433 #[test]
2434 fn schema_map_has_children() {
2435 let map = Are::schema()
2436 .iter()
2437 .find(|f| f.label == "Map")
2438 .expect("Map field must exist in schema");
2439 assert!(map.children.is_some());
2440 assert_eq!(
2441 map.children.expect("Map children must be present").len(),
2442 11
2443 );
2444 }
2445}