1use std::io::{Cursor, Read, Write};
50
51use crate::gff_helpers::{
52 get_bool, get_f32, get_i32, get_locstring, get_resref, get_string, get_u16, get_u32, get_u8,
53 upsert_field,
54};
55use rakata_core::{ResRef, StrRef};
56use rakata_formats::{
57 gff_schema::{FieldConstraint, FieldSchema, GffSchema, GffType},
58 read_gff, read_gff_from_bytes, write_gff, Gff, GffBinaryError, GffLocalizedString, GffStruct,
59 GffValue,
60};
61use thiserror::Error;
62
63#[derive(Debug, Clone, PartialEq)]
65pub struct Are {
66 pub unused_id: i32,
68 pub creator_id: i32,
70 pub version: u32,
72 pub tag: String,
74 pub name: GffLocalizedString,
76 pub comment: String,
78 pub alpha_test: f32,
80 pub camera_style: i32,
82 pub default_envmap: ResRef,
84 pub restrict_mode: u8,
86 pub grass_texture: ResRef,
88 pub grass_density: f32,
90 pub grass_size: f32,
92 pub grass_prob_ll: f32,
94 pub grass_prob_lr: f32,
96 pub grass_prob_ul: f32,
98 pub grass_prob_ur: f32,
100 pub fog_enabled: bool,
102 pub fog_near: f32,
104 pub fog_far: f32,
106 pub shadows: bool,
108 pub shadow_opacity: u8,
110 pub wind_power: i32,
112 pub unescapable: bool,
114 pub disable_transit: bool,
116 pub stealth_xp: bool,
118 pub stealth_xp_loss: u32,
120 pub stealth_xp_max: u32,
122 pub stealth_xp_current: u32,
124 pub on_enter: ResRef,
126 pub on_exit: ResRef,
128 pub on_heartbeat: ResRef,
130 pub on_user_defined: ResRef,
132 pub flags: u32,
134 pub loadscreen_id: u16,
136 pub chance_rain: i32,
138 pub chance_snow: i32,
140 pub chance_lightning: i32,
142 pub chance_fog: i32,
144 pub mod_spot_check: i32,
146 pub mod_listen_check: i32,
148 pub moon_ambient_color: u32,
150 pub moon_diffuse_color: u32,
152 pub moon_fog_enabled: bool,
154 pub moon_fog_near: f32,
156 pub moon_fog_far: f32,
158 pub moon_fog_color: u32,
160 pub moon_shadows: bool,
162 pub sun_ambient_color: u32,
164 pub sun_diffuse_color: u32,
166 pub dynamic_ambient_color: u32,
168 pub sun_fog_color: u32,
170 pub grass_ambient_color: u32,
172 pub grass_diffuse_color: u32,
174 pub grass_emissive_color: u32,
176 pub dirty_argb_one: i32,
178 pub dirty_size_one: i32,
180 pub dirty_formula_one: i32,
182 pub dirty_func_one: i32,
184 pub dirty_argb_two: i32,
186 pub dirty_size_two: i32,
188 pub dirty_formula_two: i32,
190 pub dirty_func_two: i32,
192 pub dirty_argb_three: i32,
194 pub dirty_size_three: i32,
196 pub dirty_formula_three: i32,
198 pub dirty_func_three: i32,
200 pub is_night: bool,
202 pub lighting_scheme: u8,
204 pub day_night_cycle: u8,
206 pub no_rest: bool,
208 pub no_hang_back: bool,
210 pub player_only: bool,
212 pub player_vs_player: u8,
214 pub trans_pending: u8,
216 pub trans_pend_next_id: u8,
218 pub trans_pend_curr_id: u8,
220 pub map: AreMap,
222 pub rooms: Vec<AreRoom>,
224 pub expansion_list: Vec<AreExpansionEntry>,
226 pub mini_game: Option<AreMiniGame>,
228}
229
230#[derive(Debug, Clone, PartialEq)]
232pub struct AreMiniGame {
233 pub mini_game_type: u32,
235 pub movement_per_sec: f32,
237 pub lateral_accel: f32,
239 pub bump_plane: u32,
241 pub do_bumping: bool,
243 pub use_inertia: bool,
245 pub dof: u32,
247 pub music: ResRef,
249 pub far_clip: f32,
251 pub near_clip: f32,
253 pub camera_view_angle: f32,
255 pub player: Option<AreMiniGamePlayer>,
257}
258
259impl Default for AreMiniGame {
260 fn default() -> Self {
261 Self {
262 mini_game_type: 0,
263 movement_per_sec: 0.0,
264 lateral_accel: 60.0,
265 bump_plane: 0,
266 do_bumping: false,
267 use_inertia: false,
268 dof: 0,
269 music: ResRef::blank(),
270 far_clip: 100.0,
271 near_clip: 0.1,
272 camera_view_angle: 65.0,
273 player: None,
274 }
275 }
276}
277
278impl AreMiniGame {
279 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
280 let player = match structure.field("Player") {
281 Some(GffValue::Struct(s)) => Some(AreMiniGamePlayer::from_struct(s)?),
282 Some(_) => {
283 return Err(AreError::TypeMismatch {
284 field: "MiniGame.Player",
285 expected: "Struct",
286 });
287 }
288 None => None,
289 };
290
291 Ok(Self {
292 mini_game_type: get_u32(structure, "Type").unwrap_or(0),
293 movement_per_sec: get_f32(structure, "MovementPerSec").unwrap_or(0.0),
294 lateral_accel: get_f32(structure, "LateralAccel").unwrap_or(60.0),
295 bump_plane: get_u32(structure, "Bump_Plane").unwrap_or(0),
296 do_bumping: get_bool(structure, "DoBumping").unwrap_or(false),
297 use_inertia: get_bool(structure, "UseInertia").unwrap_or(false),
298 dof: get_u32(structure, "DOF").unwrap_or(0),
299 music: get_resref(structure, "Music").unwrap_or_default(),
300 far_clip: get_f32(structure, "Far_Clip").unwrap_or(100.0),
301 near_clip: get_f32(structure, "Near_Clip").unwrap_or(0.1),
302 camera_view_angle: get_f32(structure, "CameraViewAngle").unwrap_or(65.0),
303 player,
304 })
305 }
306
307 fn to_struct(&self) -> GffStruct {
308 let mut s = GffStruct::new(0);
309 upsert_field(&mut s, "Type", GffValue::UInt32(self.mini_game_type));
310 upsert_field(
311 &mut s,
312 "MovementPerSec",
313 GffValue::Single(self.movement_per_sec),
314 );
315 upsert_field(&mut s, "LateralAccel", GffValue::Single(self.lateral_accel));
316 upsert_field(&mut s, "Bump_Plane", GffValue::UInt32(self.bump_plane));
317 upsert_field(
318 &mut s,
319 "DoBumping",
320 GffValue::UInt8(u8::from(self.do_bumping)),
321 );
322 upsert_field(
323 &mut s,
324 "UseInertia",
325 GffValue::UInt8(u8::from(self.use_inertia)),
326 );
327 upsert_field(&mut s, "DOF", GffValue::UInt32(self.dof));
328 upsert_field(&mut s, "Music", GffValue::ResRef(self.music));
329 upsert_field(&mut s, "Far_Clip", GffValue::Single(self.far_clip));
330 upsert_field(&mut s, "Near_Clip", GffValue::Single(self.near_clip));
331 upsert_field(
332 &mut s,
333 "CameraViewAngle",
334 GffValue::Single(self.camera_view_angle),
335 );
336 if let Some(player) = &self.player {
337 upsert_field(
338 &mut s,
339 "Player",
340 GffValue::Struct(Box::new(player.to_struct())),
341 );
342 }
343 s
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Default)]
349pub struct AreMiniGamePlayer {
350 pub models: Vec<AreMiniGameModel>,
352 pub track: ResRef,
354 pub camera: ResRef,
356 pub camera_rotate: bool,
358 pub mouse: AreMiniGameMouse,
360 pub enemies: Vec<AreMiniGameEnemy>,
362 pub obstacles: Vec<AreMiniGameObstacle>,
364}
365
366impl AreMiniGamePlayer {
367 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
368 let models = match structure.field("Models") {
369 Some(GffValue::List(items)) => items
370 .iter()
371 .map(AreMiniGameModel::from_struct)
372 .collect::<Vec<_>>(),
373 Some(_) => {
374 return Err(AreError::TypeMismatch {
375 field: "MiniGame.Player.Models",
376 expected: "List",
377 });
378 }
379 None => Vec::new(),
380 };
381
382 let mouse = match structure.field("Mouse") {
383 Some(GffValue::Struct(s)) => AreMiniGameMouse::from_struct(s),
384 Some(_) => {
385 return Err(AreError::TypeMismatch {
386 field: "MiniGame.Player.Mouse",
387 expected: "Struct",
388 });
389 }
390 None => AreMiniGameMouse::default(),
391 };
392
393 let enemies = match structure.field("Enemies") {
394 Some(GffValue::List(items)) => items
395 .iter()
396 .map(AreMiniGameEnemy::from_struct)
397 .collect::<Result<Vec<_>, _>>()?,
398 Some(_) => {
399 return Err(AreError::TypeMismatch {
400 field: "MiniGame.Player.Enemies",
401 expected: "List",
402 });
403 }
404 None => Vec::new(),
405 };
406
407 let obstacles = match structure.field("Obstacles") {
408 Some(GffValue::List(items)) => items
409 .iter()
410 .map(AreMiniGameObstacle::from_struct)
411 .collect::<Vec<_>>(),
412 Some(_) => {
413 return Err(AreError::TypeMismatch {
414 field: "MiniGame.Player.Obstacles",
415 expected: "List",
416 });
417 }
418 None => Vec::new(),
419 };
420
421 Ok(Self {
422 models,
423 track: get_resref(structure, "Track").unwrap_or_default(),
424 camera: get_resref(structure, "Camera").unwrap_or_default(),
425 camera_rotate: get_bool(structure, "CameraRotate").unwrap_or(false),
426 mouse,
427 enemies,
428 obstacles,
429 })
430 }
431
432 fn to_struct(&self) -> GffStruct {
433 let mut s = GffStruct::new(0);
434 let model_structs: Vec<GffStruct> = self
435 .models
436 .iter()
437 .map(AreMiniGameModel::to_struct)
438 .collect();
439 upsert_field(&mut s, "Models", GffValue::List(model_structs));
440 upsert_field(&mut s, "Track", GffValue::ResRef(self.track));
441 upsert_field(&mut s, "Camera", GffValue::ResRef(self.camera));
442 upsert_field(
443 &mut s,
444 "CameraRotate",
445 GffValue::UInt8(u8::from(self.camera_rotate)),
446 );
447 upsert_field(
448 &mut s,
449 "Mouse",
450 GffValue::Struct(Box::new(self.mouse.to_struct())),
451 );
452 let enemy_structs: Vec<GffStruct> = self
453 .enemies
454 .iter()
455 .map(AreMiniGameEnemy::to_struct)
456 .collect();
457 upsert_field(&mut s, "Enemies", GffValue::List(enemy_structs));
458 let obstacle_structs: Vec<GffStruct> = self
459 .obstacles
460 .iter()
461 .map(AreMiniGameObstacle::to_struct)
462 .collect();
463 upsert_field(&mut s, "Obstacles", GffValue::List(obstacle_structs));
464 s
465 }
466}
467
468#[derive(Debug, Clone, PartialEq)]
470pub struct AreMiniGameModel {
471 pub model: ResRef,
473 pub rotating_model: bool,
475}
476
477impl Default for AreMiniGameModel {
478 fn default() -> Self {
479 Self {
480 model: ResRef::blank(),
481 rotating_model: true,
482 }
483 }
484}
485
486impl AreMiniGameModel {
487 fn from_struct(structure: &GffStruct) -> Self {
488 Self {
489 model: get_resref(structure, "Model").unwrap_or_default(),
490 rotating_model: get_bool(structure, "RotatingModel").unwrap_or(true),
491 }
492 }
493
494 fn to_struct(&self) -> GffStruct {
495 let mut s = GffStruct::new(0);
496 upsert_field(&mut s, "Model", GffValue::ResRef(self.model));
497 upsert_field(
498 &mut s,
499 "RotatingModel",
500 GffValue::UInt8(u8::from(self.rotating_model)),
501 );
502 s
503 }
504}
505
506#[derive(Debug, Clone, PartialEq, Default)]
508pub struct AreMiniGameMouse {
509 pub axis_x: u32,
511 pub axis_y: u32,
513 pub flip_axis_x: bool,
515 pub flip_axis_y: bool,
517}
518
519impl AreMiniGameMouse {
520 fn from_struct(structure: &GffStruct) -> Self {
521 Self {
522 axis_x: get_u32(structure, "AxisX").unwrap_or(0),
523 axis_y: get_u32(structure, "AxisY").unwrap_or(0),
524 flip_axis_x: get_bool(structure, "FlipAxisX").unwrap_or(false),
525 flip_axis_y: get_bool(structure, "FlipAxisY").unwrap_or(false),
526 }
527 }
528
529 fn to_struct(&self) -> GffStruct {
530 let mut s = GffStruct::new(0);
531 upsert_field(&mut s, "AxisX", GffValue::UInt32(self.axis_x));
532 upsert_field(&mut s, "AxisY", GffValue::UInt32(self.axis_y));
533 upsert_field(
534 &mut s,
535 "FlipAxisX",
536 GffValue::UInt8(u8::from(self.flip_axis_x)),
537 );
538 upsert_field(
539 &mut s,
540 "FlipAxisY",
541 GffValue::UInt8(u8::from(self.flip_axis_y)),
542 );
543 s
544 }
545}
546
547#[derive(Debug, Clone, PartialEq, Default)]
549pub struct AreMiniGameEnemy {
550 pub models: Vec<AreMiniGameModel>,
552 pub track: ResRef,
554}
555
556impl AreMiniGameEnemy {
557 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
558 let models = match structure.field("Models") {
559 Some(GffValue::List(items)) => items
560 .iter()
561 .map(AreMiniGameModel::from_struct)
562 .collect::<Vec<_>>(),
563 Some(_) => {
564 return Err(AreError::TypeMismatch {
565 field: "MiniGame.Player.Enemies[].Models",
566 expected: "List",
567 });
568 }
569 None => Vec::new(),
570 };
571
572 Ok(Self {
573 models,
574 track: get_resref(structure, "Track").unwrap_or_default(),
575 })
576 }
577
578 fn to_struct(&self) -> GffStruct {
579 let mut s = GffStruct::new(0);
580 let model_structs: Vec<GffStruct> = self
581 .models
582 .iter()
583 .map(AreMiniGameModel::to_struct)
584 .collect();
585 upsert_field(&mut s, "Models", GffValue::List(model_structs));
586 upsert_field(&mut s, "Track", GffValue::ResRef(self.track));
587 s
588 }
589}
590
591#[derive(Debug, Clone, PartialEq, Default)]
593pub struct AreMiniGameObstacle {
594 pub name: ResRef,
596}
597
598impl AreMiniGameObstacle {
599 fn from_struct(structure: &GffStruct) -> Self {
600 Self {
601 name: get_resref(structure, "Name").unwrap_or_default(),
602 }
603 }
604
605 fn to_struct(&self) -> GffStruct {
606 let mut s = GffStruct::new(0);
607 upsert_field(&mut s, "Name", GffValue::ResRef(self.name));
608 s
609 }
610}
611
612impl Default for Are {
613 fn default() -> Self {
614 Self {
615 unused_id: 0,
616 creator_id: 0,
617 version: 0,
618 tag: String::new(),
619 name: GffLocalizedString::new(StrRef::invalid()),
620 comment: String::new(),
621 alpha_test: 0.0,
622 camera_style: 0,
623 default_envmap: ResRef::blank(),
624 restrict_mode: 0,
625 grass_texture: ResRef::blank(),
626 grass_density: 0.0,
627 grass_size: 0.0,
628 grass_prob_ll: 0.0,
629 grass_prob_lr: 0.0,
630 grass_prob_ul: 0.0,
631 grass_prob_ur: 0.0,
632 fog_enabled: false,
633 fog_near: 0.0,
634 fog_far: 0.0,
635 shadows: false,
636 shadow_opacity: 0,
637 wind_power: 0,
638 unescapable: false,
639 disable_transit: false,
640 stealth_xp: false,
641 stealth_xp_loss: 0,
642 stealth_xp_max: 0,
643 stealth_xp_current: 0,
644 on_enter: ResRef::blank(),
645 on_exit: ResRef::blank(),
646 on_heartbeat: ResRef::blank(),
647 on_user_defined: ResRef::blank(),
648 flags: 0,
649 loadscreen_id: 0,
650 chance_rain: 0,
651 chance_snow: 0,
652 chance_lightning: 0,
653 chance_fog: 0,
654 mod_spot_check: 0,
655 mod_listen_check: 0,
656 moon_ambient_color: 0,
657 moon_diffuse_color: 0,
658 moon_fog_enabled: false,
659 moon_fog_near: 0.0,
660 moon_fog_far: 0.0,
661 moon_fog_color: 0,
662 moon_shadows: false,
663 sun_ambient_color: 0,
664 sun_diffuse_color: 0,
665 dynamic_ambient_color: 0,
666 sun_fog_color: 0,
667 grass_ambient_color: 0,
668 grass_diffuse_color: 0,
669 grass_emissive_color: 0,
670 dirty_argb_one: 0,
671 dirty_size_one: 0,
672 dirty_formula_one: 0,
673 dirty_func_one: 0,
674 dirty_argb_two: 0,
675 dirty_size_two: 0,
676 dirty_formula_two: 0,
677 dirty_func_two: 0,
678 dirty_argb_three: 0,
679 dirty_size_three: 0,
680 dirty_formula_three: 0,
681 dirty_func_three: 0,
682 is_night: false,
683 lighting_scheme: 0,
684 day_night_cycle: 0,
685 no_rest: false,
686 no_hang_back: false,
687 player_only: false,
688 player_vs_player: 0,
689 trans_pending: 0,
690 trans_pend_next_id: 0,
691 trans_pend_curr_id: 0,
692 map: AreMap::default(),
693 rooms: Vec::new(),
694 expansion_list: Vec::new(),
695 mini_game: None,
696 }
697 }
698}
699
700impl Are {
701 pub fn new() -> Self {
703 Self::default()
704 }
705
706 pub fn from_gff(gff: &Gff) -> Result<Self, AreError> {
708 if gff.file_type != *b"ARE " && gff.file_type != *b"GFF " {
709 return Err(AreError::UnsupportedFileType(gff.file_type));
710 }
711
712 let root = &gff.root;
713
714 let map = match root.field("Map") {
715 Some(GffValue::Struct(map_struct)) => AreMap::from_struct(map_struct),
716 Some(_) => {
717 return Err(AreError::TypeMismatch {
718 field: "Map",
719 expected: "Struct",
720 });
721 }
722 None => AreMap::default(),
723 };
724
725 let rooms = match root.field("Rooms") {
726 Some(GffValue::List(room_structs)) => room_structs
727 .iter()
728 .map(AreRoom::from_struct)
729 .collect::<Result<Vec<_>, _>>()?,
730 Some(_) => {
731 return Err(AreError::TypeMismatch {
732 field: "Rooms",
733 expected: "List",
734 });
735 }
736 None => Vec::new(),
737 };
738
739 let expansion_list = match root.field("Expansion_List") {
740 Some(GffValue::List(expansion_structs)) => expansion_structs
741 .iter()
742 .map(AreExpansionEntry::from_struct)
743 .collect::<Result<Vec<_>, _>>()?,
744 Some(_) => {
745 return Err(AreError::TypeMismatch {
746 field: "Expansion_List",
747 expected: "List",
748 });
749 }
750 None => Vec::new(),
751 };
752
753 let mini_game = match root.field("MiniGame") {
754 Some(GffValue::Struct(s)) => Some(AreMiniGame::from_struct(s)?),
755 Some(_) => {
756 return Err(AreError::TypeMismatch {
757 field: "MiniGame",
758 expected: "Struct",
759 });
760 }
761 None => None,
762 };
763
764 Ok(Self {
765 unused_id: get_i32(root, "ID").unwrap_or(0),
766 creator_id: get_i32(root, "Creator_ID").unwrap_or(0),
767 version: get_u32(root, "Version").unwrap_or(0),
768 tag: get_string(root, "Tag").unwrap_or_default(),
769 name: get_locstring(root, "Name")
770 .cloned()
771 .unwrap_or_else(|| GffLocalizedString::new(StrRef::invalid())),
772 comment: get_string(root, "Comments").unwrap_or_default(),
773 alpha_test: get_f32(root, "AlphaTest").unwrap_or(0.0),
774 camera_style: get_i32(root, "CameraStyle").unwrap_or(0),
775 default_envmap: get_resref(root, "DefaultEnvMap").unwrap_or_default(),
776 restrict_mode: get_u8(root, "RestrictMode").unwrap_or(0),
777 grass_texture: get_resref(root, "Grass_TexName").unwrap_or_default(),
778 grass_density: get_f32(root, "Grass_Density").unwrap_or(0.0),
779 grass_size: get_f32(root, "Grass_QuadSize").unwrap_or(0.0),
780 grass_prob_ll: get_f32(root, "Grass_Prob_LL").unwrap_or(0.0),
781 grass_prob_lr: get_f32(root, "Grass_Prob_LR").unwrap_or(0.0),
782 grass_prob_ul: get_f32(root, "Grass_Prob_UL").unwrap_or(0.0),
783 grass_prob_ur: get_f32(root, "Grass_Prob_UR").unwrap_or(0.0),
784 fog_enabled: get_bool(root, "SunFogOn").unwrap_or(false),
785 fog_near: get_f32(root, "SunFogNear").unwrap_or(0.0),
786 fog_far: get_f32(root, "SunFogFar").unwrap_or(0.0),
787 shadows: get_bool(root, "SunShadows").unwrap_or(false),
788 shadow_opacity: get_u8(root, "ShadowOpacity").unwrap_or(0),
789 wind_power: get_i32(root, "WindPower").unwrap_or(0),
790 unescapable: get_bool(root, "Unescapable").unwrap_or(false),
791 disable_transit: get_bool(root, "DisableTransit").unwrap_or(false),
792 stealth_xp: get_bool(root, "StealthXPEnabled").unwrap_or(false),
793 stealth_xp_loss: get_u32(root, "StealthXPLoss").unwrap_or(0),
794 stealth_xp_max: get_u32(root, "StealthXPMax").unwrap_or(0),
795 stealth_xp_current: get_u32(root, "StealthXPCurrent").unwrap_or(0),
796 on_enter: get_resref(root, "OnEnter").unwrap_or_default(),
797 on_exit: get_resref(root, "OnExit").unwrap_or_default(),
798 on_heartbeat: get_resref(root, "OnHeartbeat").unwrap_or_default(),
799 on_user_defined: get_resref(root, "OnUserDefined").unwrap_or_default(),
800 flags: get_u32(root, "Flags").unwrap_or(0),
801 loadscreen_id: get_u16(root, "LoadScreenID").unwrap_or(0),
802 chance_rain: get_i32(root, "ChanceRain").unwrap_or(0),
803 chance_snow: get_i32(root, "ChanceSnow").unwrap_or(0),
804 chance_lightning: get_i32(root, "ChanceLightning").unwrap_or(0),
805 chance_fog: get_i32(root, "ChanceFog").unwrap_or(0),
806 mod_spot_check: get_i32(root, "ModSpotCheck").unwrap_or(0),
807 mod_listen_check: get_i32(root, "ModListenCheck").unwrap_or(0),
808 moon_ambient_color: get_u32(root, "MoonAmbientColor").unwrap_or(0),
809 moon_diffuse_color: get_u32(root, "MoonDiffuseColor").unwrap_or(0),
810 moon_fog_enabled: get_bool(root, "MoonFogOn").unwrap_or(false),
811 moon_fog_near: get_f32(root, "MoonFogNear").unwrap_or(0.0),
812 moon_fog_far: get_f32(root, "MoonFogFar").unwrap_or(0.0),
813 moon_fog_color: get_u32(root, "MoonFogColor").unwrap_or(0),
814 moon_shadows: get_bool(root, "MoonShadows").unwrap_or(false),
815 sun_ambient_color: get_u32(root, "SunAmbientColor").unwrap_or(0),
816 sun_diffuse_color: get_u32(root, "SunDiffuseColor").unwrap_or(0),
817 dynamic_ambient_color: get_u32(root, "DynAmbientColor").unwrap_or(0),
818 sun_fog_color: get_u32(root, "SunFogColor").unwrap_or(0),
819 grass_ambient_color: get_u32(root, "Grass_Ambient").unwrap_or(0),
820 grass_diffuse_color: get_u32(root, "Grass_Diffuse").unwrap_or(0),
821 grass_emissive_color: get_u32(root, "Grass_Emissive").unwrap_or(0),
822 dirty_argb_one: get_i32(root, "DirtyARGBOne").unwrap_or(0),
823 dirty_size_one: get_i32(root, "DirtySizeOne").unwrap_or(0),
824 dirty_formula_one: get_i32(root, "DirtyFormulaOne").unwrap_or(0),
825 dirty_func_one: get_i32(root, "DirtyFuncOne").unwrap_or(0),
826 dirty_argb_two: get_i32(root, "DirtyARGBTwo").unwrap_or(0),
827 dirty_size_two: get_i32(root, "DirtySizeTwo").unwrap_or(0),
828 dirty_formula_two: get_i32(root, "DirtyFormulaTwo").unwrap_or(0),
829 dirty_func_two: get_i32(root, "DirtyFuncTwo").unwrap_or(0),
830 dirty_argb_three: get_i32(root, "DirtyARGBThree").unwrap_or(0),
831 dirty_size_three: get_i32(root, "DirtySizeThree").unwrap_or(0),
832 dirty_formula_three: get_i32(root, "DirtyFormulaThre").unwrap_or(0),
833 dirty_func_three: get_i32(root, "DirtyFuncThree").unwrap_or(0),
834 is_night: get_bool(root, "IsNight").unwrap_or(false),
835 lighting_scheme: get_u8(root, "LightingScheme").unwrap_or(0),
836 day_night_cycle: get_u8(root, "DayNightCycle").unwrap_or(0),
837 no_rest: get_bool(root, "NoRest").unwrap_or(false),
838 no_hang_back: get_bool(root, "NoHangBack").unwrap_or(false),
839 player_only: get_bool(root, "PlayerOnly").unwrap_or(false),
840 player_vs_player: get_u8(root, "PlayerVsPlayer").unwrap_or(0),
841 trans_pending: get_u8(root, "TransPending").unwrap_or(0),
842 trans_pend_next_id: get_u8(root, "TransPendNextID").unwrap_or(0),
843 trans_pend_curr_id: get_u8(root, "TransPendCurrID").unwrap_or(0),
844 map,
845 rooms,
846 expansion_list,
847 mini_game,
848 })
849 }
850
851 pub fn to_gff(&self) -> Gff {
853 let mut root = GffStruct::new(-1);
854
855 upsert_field(&mut root, "ID", GffValue::Int32(self.unused_id));
856 upsert_field(&mut root, "Creator_ID", GffValue::Int32(self.creator_id));
857 upsert_field(&mut root, "Version", GffValue::UInt32(self.version));
858 upsert_field(&mut root, "Tag", GffValue::String(self.tag.clone()));
859 upsert_field(
860 &mut root,
861 "Name",
862 GffValue::LocalizedString(self.name.clone()),
863 );
864 upsert_field(
865 &mut root,
866 "Comments",
867 GffValue::String(self.comment.clone()),
868 );
869 upsert_field(&mut root, "AlphaTest", GffValue::Single(self.alpha_test));
870 upsert_field(&mut root, "CameraStyle", GffValue::Int32(self.camera_style));
871 upsert_field(
872 &mut root,
873 "DefaultEnvMap",
874 GffValue::ResRef(self.default_envmap),
875 );
876 upsert_field(
877 &mut root,
878 "RestrictMode",
879 GffValue::UInt8(self.restrict_mode),
880 );
881 upsert_field(
882 &mut root,
883 "Grass_TexName",
884 GffValue::ResRef(self.grass_texture),
885 );
886 upsert_field(
887 &mut root,
888 "Grass_Density",
889 GffValue::Single(self.grass_density),
890 );
891 upsert_field(
892 &mut root,
893 "Grass_QuadSize",
894 GffValue::Single(self.grass_size),
895 );
896 upsert_field(
897 &mut root,
898 "Grass_Prob_LL",
899 GffValue::Single(self.grass_prob_ll),
900 );
901 upsert_field(
902 &mut root,
903 "Grass_Prob_LR",
904 GffValue::Single(self.grass_prob_lr),
905 );
906 upsert_field(
907 &mut root,
908 "Grass_Prob_UL",
909 GffValue::Single(self.grass_prob_ul),
910 );
911 upsert_field(
912 &mut root,
913 "Grass_Prob_UR",
914 GffValue::Single(self.grass_prob_ur),
915 );
916 upsert_field(
917 &mut root,
918 "SunFogOn",
919 GffValue::UInt8(u8::from(self.fog_enabled)),
920 );
921 upsert_field(&mut root, "SunFogNear", GffValue::Single(self.fog_near));
922 upsert_field(&mut root, "SunFogFar", GffValue::Single(self.fog_far));
923 upsert_field(
924 &mut root,
925 "SunShadows",
926 GffValue::UInt8(u8::from(self.shadows)),
927 );
928 upsert_field(
929 &mut root,
930 "ShadowOpacity",
931 GffValue::UInt8(self.shadow_opacity),
932 );
933 upsert_field(&mut root, "WindPower", GffValue::Int32(self.wind_power));
934 upsert_field(
935 &mut root,
936 "Unescapable",
937 GffValue::UInt8(u8::from(self.unescapable)),
938 );
939 upsert_field(
940 &mut root,
941 "DisableTransit",
942 GffValue::UInt8(u8::from(self.disable_transit)),
943 );
944 upsert_field(
945 &mut root,
946 "StealthXPEnabled",
947 GffValue::UInt8(u8::from(self.stealth_xp)),
948 );
949 upsert_field(
950 &mut root,
951 "StealthXPLoss",
952 GffValue::UInt32(self.stealth_xp_loss),
953 );
954 upsert_field(
955 &mut root,
956 "StealthXPMax",
957 GffValue::UInt32(self.stealth_xp_max),
958 );
959 upsert_field(
960 &mut root,
961 "StealthXPCurrent",
962 GffValue::UInt32(self.stealth_xp_current),
963 );
964 upsert_field(&mut root, "OnEnter", GffValue::ResRef(self.on_enter));
965 upsert_field(&mut root, "OnExit", GffValue::ResRef(self.on_exit));
966 upsert_field(
967 &mut root,
968 "OnHeartbeat",
969 GffValue::ResRef(self.on_heartbeat),
970 );
971 upsert_field(
972 &mut root,
973 "OnUserDefined",
974 GffValue::ResRef(self.on_user_defined),
975 );
976 upsert_field(&mut root, "Flags", GffValue::UInt32(self.flags));
977 upsert_field(
978 &mut root,
979 "LoadScreenID",
980 GffValue::UInt16(self.loadscreen_id),
981 );
982 upsert_field(&mut root, "ChanceRain", GffValue::Int32(self.chance_rain));
983 upsert_field(&mut root, "ChanceSnow", GffValue::Int32(self.chance_snow));
984 upsert_field(
985 &mut root,
986 "ChanceLightning",
987 GffValue::Int32(self.chance_lightning),
988 );
989 upsert_field(&mut root, "ChanceFog", GffValue::Int32(self.chance_fog));
990 upsert_field(
991 &mut root,
992 "ModSpotCheck",
993 GffValue::Int32(self.mod_spot_check),
994 );
995 upsert_field(
996 &mut root,
997 "ModListenCheck",
998 GffValue::Int32(self.mod_listen_check),
999 );
1000 upsert_field(
1001 &mut root,
1002 "MoonAmbientColor",
1003 GffValue::UInt32(self.moon_ambient_color),
1004 );
1005 upsert_field(
1006 &mut root,
1007 "MoonDiffuseColor",
1008 GffValue::UInt32(self.moon_diffuse_color),
1009 );
1010 upsert_field(
1011 &mut root,
1012 "MoonFogOn",
1013 GffValue::UInt8(u8::from(self.moon_fog_enabled)),
1014 );
1015 upsert_field(
1016 &mut root,
1017 "MoonFogNear",
1018 GffValue::Single(self.moon_fog_near),
1019 );
1020 upsert_field(&mut root, "MoonFogFar", GffValue::Single(self.moon_fog_far));
1021 upsert_field(
1022 &mut root,
1023 "MoonFogColor",
1024 GffValue::UInt32(self.moon_fog_color),
1025 );
1026 upsert_field(
1027 &mut root,
1028 "MoonShadows",
1029 GffValue::UInt8(u8::from(self.moon_shadows)),
1030 );
1031 upsert_field(
1032 &mut root,
1033 "SunAmbientColor",
1034 GffValue::UInt32(self.sun_ambient_color),
1035 );
1036 upsert_field(
1037 &mut root,
1038 "SunDiffuseColor",
1039 GffValue::UInt32(self.sun_diffuse_color),
1040 );
1041 upsert_field(
1042 &mut root,
1043 "DynAmbientColor",
1044 GffValue::UInt32(self.dynamic_ambient_color),
1045 );
1046 upsert_field(
1047 &mut root,
1048 "SunFogColor",
1049 GffValue::UInt32(self.sun_fog_color),
1050 );
1051 upsert_field(
1052 &mut root,
1053 "Grass_Ambient",
1054 GffValue::UInt32(self.grass_ambient_color),
1055 );
1056 upsert_field(
1057 &mut root,
1058 "Grass_Diffuse",
1059 GffValue::UInt32(self.grass_diffuse_color),
1060 );
1061 upsert_field(
1062 &mut root,
1063 "Grass_Emissive",
1064 GffValue::UInt32(self.grass_emissive_color),
1065 );
1066 upsert_field(
1067 &mut root,
1068 "DirtyARGBOne",
1069 GffValue::Int32(self.dirty_argb_one),
1070 );
1071 upsert_field(
1072 &mut root,
1073 "DirtySizeOne",
1074 GffValue::Int32(self.dirty_size_one),
1075 );
1076 upsert_field(
1077 &mut root,
1078 "DirtyFormulaOne",
1079 GffValue::Int32(self.dirty_formula_one),
1080 );
1081 upsert_field(
1082 &mut root,
1083 "DirtyFuncOne",
1084 GffValue::Int32(self.dirty_func_one),
1085 );
1086 upsert_field(
1087 &mut root,
1088 "DirtyARGBTwo",
1089 GffValue::Int32(self.dirty_argb_two),
1090 );
1091 upsert_field(
1092 &mut root,
1093 "DirtySizeTwo",
1094 GffValue::Int32(self.dirty_size_two),
1095 );
1096 upsert_field(
1097 &mut root,
1098 "DirtyFormulaTwo",
1099 GffValue::Int32(self.dirty_formula_two),
1100 );
1101 upsert_field(
1102 &mut root,
1103 "DirtyFuncTwo",
1104 GffValue::Int32(self.dirty_func_two),
1105 );
1106 upsert_field(
1107 &mut root,
1108 "DirtyARGBThree",
1109 GffValue::Int32(self.dirty_argb_three),
1110 );
1111 upsert_field(
1112 &mut root,
1113 "DirtySizeThree",
1114 GffValue::Int32(self.dirty_size_three),
1115 );
1116 upsert_field(
1117 &mut root,
1118 "DirtyFormulaThre",
1119 GffValue::Int32(self.dirty_formula_three),
1120 );
1121 upsert_field(
1122 &mut root,
1123 "DirtyFuncThree",
1124 GffValue::Int32(self.dirty_func_three),
1125 );
1126 upsert_field(
1127 &mut root,
1128 "IsNight",
1129 GffValue::UInt8(u8::from(self.is_night)),
1130 );
1131 upsert_field(
1132 &mut root,
1133 "LightingScheme",
1134 GffValue::UInt8(self.lighting_scheme),
1135 );
1136 upsert_field(
1137 &mut root,
1138 "DayNightCycle",
1139 GffValue::UInt8(self.day_night_cycle),
1140 );
1141 upsert_field(&mut root, "NoRest", GffValue::UInt8(u8::from(self.no_rest)));
1142 upsert_field(
1143 &mut root,
1144 "NoHangBack",
1145 GffValue::UInt8(u8::from(self.no_hang_back)),
1146 );
1147 upsert_field(
1148 &mut root,
1149 "PlayerOnly",
1150 GffValue::UInt8(u8::from(self.player_only)),
1151 );
1152 upsert_field(
1153 &mut root,
1154 "PlayerVsPlayer",
1155 GffValue::UInt8(self.player_vs_player),
1156 );
1157 upsert_field(
1158 &mut root,
1159 "TransPending",
1160 GffValue::UInt8(self.trans_pending),
1161 );
1162 upsert_field(
1163 &mut root,
1164 "TransPendNextID",
1165 GffValue::UInt8(self.trans_pend_next_id),
1166 );
1167 upsert_field(
1168 &mut root,
1169 "TransPendCurrID",
1170 GffValue::UInt8(self.trans_pend_curr_id),
1171 );
1172
1173 let map_struct = self.map.to_struct();
1174 upsert_field(&mut root, "Map", GffValue::Struct(Box::new(map_struct)));
1175
1176 let room_structs = self
1177 .rooms
1178 .iter()
1179 .map(AreRoom::to_struct)
1180 .collect::<Vec<GffStruct>>();
1181 upsert_field(&mut root, "Rooms", GffValue::List(room_structs));
1182 let expansion_structs = self
1183 .expansion_list
1184 .iter()
1185 .map(AreExpansionEntry::to_struct)
1186 .collect::<Vec<GffStruct>>();
1187 upsert_field(
1188 &mut root,
1189 "Expansion_List",
1190 GffValue::List(expansion_structs),
1191 );
1192
1193 if let Some(mg) = &self.mini_game {
1194 upsert_field(
1195 &mut root,
1196 "MiniGame",
1197 GffValue::Struct(Box::new(mg.to_struct())),
1198 );
1199 }
1200
1201 Gff::new(*b"ARE ", root)
1202 }
1203}
1204
1205#[derive(Debug, Clone, PartialEq)]
1207pub struct AreMap {
1208 pub north_axis: i32,
1210 pub map_zoom: i32,
1212 pub map_res_x: i32,
1214 pub map_point_1: [f32; 2],
1216 pub map_point_2: [f32; 2],
1218 pub world_point_1: [f32; 2],
1220 pub world_point_2: [f32; 2],
1222}
1223
1224impl Default for AreMap {
1225 fn default() -> Self {
1226 Self {
1227 north_axis: 0,
1228 map_zoom: 0,
1229 map_res_x: 0,
1230 map_point_1: [0.0, 0.0],
1231 map_point_2: [0.0, 0.0],
1232 world_point_1: [0.0, 0.0],
1233 world_point_2: [0.0, 0.0],
1234 }
1235 }
1236}
1237
1238impl AreMap {
1239 fn from_struct(structure: &GffStruct) -> Self {
1240 Self {
1241 north_axis: get_i32(structure, "NorthAxis").unwrap_or(0),
1242 map_zoom: get_i32(structure, "MapZoom").unwrap_or(0),
1243 map_res_x: get_i32(structure, "MapResX").unwrap_or(0),
1244 map_point_1: [
1245 get_map_point_f32(structure, "MapPt1X").unwrap_or(0.0),
1246 get_map_point_f32(structure, "MapPt1Y").unwrap_or(0.0),
1247 ],
1248 map_point_2: [
1249 get_map_point_f32(structure, "MapPt2X").unwrap_or(0.0),
1250 get_map_point_f32(structure, "MapPt2Y").unwrap_or(0.0),
1251 ],
1252 world_point_1: [
1253 get_f32(structure, "WorldPt1X").unwrap_or(0.0),
1254 get_f32(structure, "WorldPt1Y").unwrap_or(0.0),
1255 ],
1256 world_point_2: [
1257 get_f32(structure, "WorldPt2X").unwrap_or(0.0),
1258 get_f32(structure, "WorldPt2Y").unwrap_or(0.0),
1259 ],
1260 }
1261 }
1262
1263 fn to_struct(&self) -> GffStruct {
1264 let mut structure = GffStruct::new(0);
1265 upsert_field(
1266 &mut structure,
1267 "NorthAxis",
1268 GffValue::Int32(self.north_axis),
1269 );
1270 upsert_field(&mut structure, "MapZoom", GffValue::Int32(self.map_zoom));
1271 upsert_field(&mut structure, "MapResX", GffValue::Int32(self.map_res_x));
1272 upsert_field(
1273 &mut structure,
1274 "MapPt1X",
1275 GffValue::Single(self.map_point_1[0]),
1276 );
1277 upsert_field(
1278 &mut structure,
1279 "MapPt1Y",
1280 GffValue::Single(self.map_point_1[1]),
1281 );
1282 upsert_field(
1283 &mut structure,
1284 "MapPt2X",
1285 GffValue::Single(self.map_point_2[0]),
1286 );
1287 upsert_field(
1288 &mut structure,
1289 "MapPt2Y",
1290 GffValue::Single(self.map_point_2[1]),
1291 );
1292 upsert_field(
1293 &mut structure,
1294 "WorldPt1X",
1295 GffValue::Single(self.world_point_1[0]),
1296 );
1297 upsert_field(
1298 &mut structure,
1299 "WorldPt1Y",
1300 GffValue::Single(self.world_point_1[1]),
1301 );
1302 upsert_field(
1303 &mut structure,
1304 "WorldPt2X",
1305 GffValue::Single(self.world_point_2[0]),
1306 );
1307 upsert_field(
1308 &mut structure,
1309 "WorldPt2Y",
1310 GffValue::Single(self.world_point_2[1]),
1311 );
1312 structure
1313 }
1314}
1315
1316#[derive(Debug, Clone, PartialEq)]
1318pub struct AreRoom {
1319 pub room_name: String,
1321 pub ambient_scale: f32,
1323 pub env_audio: i32,
1325 pub force_rating: i32,
1327 pub disable_weather: bool,
1329 pub part_sounds: Vec<ArePartSound>,
1331}
1332
1333impl AreRoom {
1334 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1335 let part_sounds = match structure.field("PartSounds") {
1336 Some(GffValue::List(part_sound_structs)) => part_sound_structs
1337 .iter()
1338 .map(ArePartSound::from_struct)
1339 .collect::<Result<Vec<_>, _>>()?,
1340 Some(_) => {
1341 return Err(AreError::TypeMismatch {
1342 field: "Rooms[].PartSounds",
1343 expected: "List",
1344 });
1345 }
1346 None => Vec::new(),
1347 };
1348
1349 Ok(Self {
1350 room_name: get_string(structure, "RoomName").unwrap_or_default(),
1351 ambient_scale: get_f32(structure, "AmbientScale").unwrap_or(0.0),
1352 env_audio: get_i32(structure, "EnvAudio").unwrap_or(0),
1353 force_rating: get_i32(structure, "ForceRating").unwrap_or(0),
1354 disable_weather: get_bool(structure, "DisableWeather").unwrap_or(false),
1355 part_sounds,
1356 })
1357 }
1358
1359 fn to_struct(&self) -> GffStruct {
1360 let mut structure = GffStruct::new(0);
1361 upsert_field(
1362 &mut structure,
1363 "RoomName",
1364 GffValue::String(self.room_name.clone()),
1365 );
1366 upsert_field(
1367 &mut structure,
1368 "AmbientScale",
1369 GffValue::Single(self.ambient_scale),
1370 );
1371 upsert_field(&mut structure, "EnvAudio", GffValue::Int32(self.env_audio));
1372 upsert_field(
1373 &mut structure,
1374 "ForceRating",
1375 GffValue::Int32(self.force_rating),
1376 );
1377 upsert_field(
1378 &mut structure,
1379 "DisableWeather",
1380 GffValue::UInt8(u8::from(self.disable_weather)),
1381 );
1382 let part_sound_structs = self
1383 .part_sounds
1384 .iter()
1385 .map(ArePartSound::to_struct)
1386 .collect::<Vec<GffStruct>>();
1387 upsert_field(
1388 &mut structure,
1389 "PartSounds",
1390 GffValue::List(part_sound_structs),
1391 );
1392 structure
1393 }
1394}
1395
1396#[derive(Debug, Clone, PartialEq)]
1398pub struct AreExpansionEntry {
1399 pub expansion_name: GffLocalizedString,
1401 pub expansion_id: i32,
1403}
1404
1405impl AreExpansionEntry {
1406 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1407 let expansion_name = match structure.field("Expansion_Name") {
1408 Some(GffValue::LocalizedString(value)) => value.clone(),
1409 Some(_) => {
1410 return Err(AreError::TypeMismatch {
1411 field: "Expansion_List[].Expansion_Name",
1412 expected: "LocalizedString",
1413 });
1414 }
1415 None => GffLocalizedString::new(StrRef::invalid()),
1416 };
1417
1418 Ok(Self {
1419 expansion_name,
1420 expansion_id: get_i32(structure, "Expansion_ID").unwrap_or(0),
1421 })
1422 }
1423
1424 fn to_struct(&self) -> GffStruct {
1425 let mut structure = GffStruct::new(0);
1426 upsert_field(
1427 &mut structure,
1428 "Expansion_Name",
1429 GffValue::LocalizedString(self.expansion_name.clone()),
1430 );
1431 upsert_field(
1432 &mut structure,
1433 "Expansion_ID",
1434 GffValue::Int32(self.expansion_id),
1435 );
1436 structure
1437 }
1438}
1439
1440#[derive(Debug, Clone, PartialEq)]
1442pub struct ArePartSound {
1443 pub looping: bool,
1445 pub model_part: String,
1447 pub omen_event: String,
1449 pub sound: ResRef,
1451}
1452
1453impl ArePartSound {
1454 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1455 let looping = match structure.field("Looping") {
1456 Some(GffValue::UInt8(value)) => *value != 0,
1457 Some(GffValue::Int8(value)) => *value != 0,
1458 Some(GffValue::UInt16(value)) => *value != 0,
1459 Some(GffValue::Int16(value)) => *value != 0,
1460 Some(GffValue::UInt32(value)) => *value != 0,
1461 Some(GffValue::Int32(value)) => *value != 0,
1462 Some(_) => {
1463 return Err(AreError::TypeMismatch {
1464 field: "Rooms[].PartSounds[].Looping",
1465 expected: "numeric bool",
1466 });
1467 }
1468 None => false,
1469 };
1470
1471 Ok(Self {
1472 looping,
1473 model_part: get_string(structure, "ModelPart").unwrap_or_default(),
1474 omen_event: get_string(structure, "OmenEvent").unwrap_or_default(),
1475 sound: get_resref(structure, "Sound").unwrap_or_default(),
1476 })
1477 }
1478
1479 fn to_struct(&self) -> GffStruct {
1480 let mut structure = GffStruct::new(0);
1481 upsert_field(
1482 &mut structure,
1483 "Looping",
1484 GffValue::UInt8(u8::from(self.looping)),
1485 );
1486 upsert_field(
1487 &mut structure,
1488 "ModelPart",
1489 GffValue::String(self.model_part.clone()),
1490 );
1491 upsert_field(
1492 &mut structure,
1493 "OmenEvent",
1494 GffValue::String(self.omen_event.clone()),
1495 );
1496 upsert_field(&mut structure, "Sound", GffValue::ResRef(self.sound));
1497 structure
1498 }
1499}
1500
1501#[derive(Debug, Error)]
1503pub enum AreError {
1504 #[error("unsupported ARE file type: {0:?}")]
1506 UnsupportedFileType([u8; 4]),
1507 #[error("ARE field `{field}` has incompatible type (expected {expected})")]
1509 TypeMismatch {
1510 field: &'static str,
1512 expected: &'static str,
1514 },
1515 #[error(transparent)]
1517 Gff(#[from] GffBinaryError),
1518}
1519
1520#[cfg_attr(
1522 feature = "tracing",
1523 tracing::instrument(level = "debug", skip(reader))
1524)]
1525pub fn read_are<R: Read>(reader: &mut R) -> Result<Are, AreError> {
1526 let gff = read_gff(reader)?;
1527 Are::from_gff(&gff)
1528}
1529
1530#[cfg_attr(
1532 feature = "tracing",
1533 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
1534)]
1535pub fn read_are_from_bytes(bytes: &[u8]) -> Result<Are, AreError> {
1536 let gff = read_gff_from_bytes(bytes)?;
1537 Are::from_gff(&gff)
1538}
1539
1540#[cfg_attr(
1542 feature = "tracing",
1543 tracing::instrument(level = "debug", skip(writer, are))
1544)]
1545pub fn write_are<W: Write>(writer: &mut W, are: &Are) -> Result<(), AreError> {
1546 let gff = are.to_gff();
1547 write_gff(writer, &gff)?;
1548 Ok(())
1549}
1550
1551#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(are)))]
1553pub fn write_are_to_vec(are: &Are) -> Result<Vec<u8>, AreError> {
1554 let mut cursor = Cursor::new(Vec::new());
1555 write_are(&mut cursor, are)?;
1556 Ok(cursor.into_inner())
1557}
1558
1559fn get_map_point_f32(structure: &GffStruct, label: &str) -> Option<f32> {
1560 match structure.field(label) {
1561 Some(GffValue::Single(value)) => Some(*value),
1562 #[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
1565 Some(GffValue::Double(value)) => Some(*value as f32),
1566 #[allow(clippy::cast_precision_loss, clippy::as_conversions)]
1567 Some(GffValue::Int32(value)) => Some(*value as f32),
1568 #[allow(clippy::cast_precision_loss, clippy::as_conversions)]
1569 Some(GffValue::UInt32(value)) => Some(*value as f32),
1570 Some(GffValue::Int16(value)) => Some(f32::from(*value)),
1571 Some(GffValue::UInt16(value)) => Some(f32::from(*value)),
1572 Some(GffValue::Int8(value)) => Some(f32::from(*value)),
1573 Some(GffValue::UInt8(value)) => Some(f32::from(*value)),
1574 _ => None,
1575 }
1576}
1577
1578static ROOMS_CHILDREN: &[FieldSchema] = &[
1580 FieldSchema {
1581 label: "RoomName",
1582 expected_type: GffType::String,
1583 required: false,
1584 children: None,
1585 constraint: None,
1586 },
1587 FieldSchema {
1588 label: "EnvAudio",
1589 expected_type: GffType::Int32,
1590 required: false,
1591 children: None,
1592 constraint: None,
1593 },
1594 FieldSchema {
1595 label: "AmbientScale",
1596 expected_type: GffType::Single,
1597 required: false,
1598 children: None,
1599 constraint: None,
1600 },
1601 FieldSchema {
1602 label: "ForceRating",
1603 expected_type: GffType::Int32,
1604 required: false,
1605 children: None,
1606 constraint: None,
1607 },
1608 FieldSchema {
1609 label: "DisableWeather",
1610 expected_type: GffType::UInt8,
1611 required: false,
1612 children: None,
1613 constraint: None,
1614 },
1615];
1616
1617static MAP_CHILDREN: &[FieldSchema] = &[
1619 FieldSchema {
1620 label: "MapResX",
1621 expected_type: GffType::Int32,
1622 required: false,
1623 children: None,
1624 constraint: None,
1625 },
1626 FieldSchema {
1627 label: "NorthAxis",
1628 expected_type: GffType::Int32,
1629 required: false,
1630 children: None,
1631 constraint: None,
1632 },
1633 FieldSchema {
1634 label: "MapZoom",
1635 expected_type: GffType::Int32,
1636 required: false,
1637 children: None,
1638 constraint: None,
1639 },
1640 FieldSchema {
1641 label: "MapPt1X",
1642 expected_type: GffType::Single,
1643 required: false,
1644 children: None,
1645 constraint: None,
1646 },
1647 FieldSchema {
1648 label: "MapPt1Y",
1649 expected_type: GffType::Single,
1650 required: false,
1651 children: None,
1652 constraint: None,
1653 },
1654 FieldSchema {
1655 label: "MapPt2X",
1656 expected_type: GffType::Single,
1657 required: false,
1658 children: None,
1659 constraint: None,
1660 },
1661 FieldSchema {
1662 label: "MapPt2Y",
1663 expected_type: GffType::Single,
1664 required: false,
1665 children: None,
1666 constraint: None,
1667 },
1668 FieldSchema {
1669 label: "WorldPt1X",
1670 expected_type: GffType::Single,
1671 required: false,
1672 children: None,
1673 constraint: None,
1674 },
1675 FieldSchema {
1676 label: "WorldPt1Y",
1677 expected_type: GffType::Single,
1678 required: false,
1679 children: None,
1680 constraint: None,
1681 },
1682 FieldSchema {
1683 label: "WorldPt2X",
1684 expected_type: GffType::Single,
1685 required: false,
1686 children: None,
1687 constraint: None,
1688 },
1689 FieldSchema {
1690 label: "WorldPt2Y",
1691 expected_type: GffType::Single,
1692 required: false,
1693 children: None,
1694 constraint: None,
1695 },
1696];
1697
1698static EXPANSION_LIST_CHILDREN: &[FieldSchema] = &[
1700 FieldSchema {
1701 label: "Expansion_Name",
1702 expected_type: GffType::LocalizedString,
1703 required: false,
1704 children: None,
1705 constraint: None,
1706 },
1707 FieldSchema {
1708 label: "Expansion_ID",
1709 expected_type: GffType::Int32,
1710 required: false,
1711 children: None,
1712 constraint: None,
1713 },
1714];
1715
1716static MINI_GAME_CHILDREN: &[FieldSchema] = &[
1718 FieldSchema {
1719 label: "Type",
1720 expected_type: GffType::UInt32,
1721 required: false,
1722 children: None,
1723 constraint: None,
1724 },
1725 FieldSchema {
1726 label: "MovementPerSec",
1727 expected_type: GffType::Single,
1728 required: false,
1729 children: None,
1730 constraint: None,
1731 },
1732 FieldSchema {
1733 label: "LateralAccel",
1734 expected_type: GffType::Single,
1735 required: false,
1736 children: None,
1737 constraint: None,
1738 },
1739 FieldSchema {
1740 label: "Bump_Plane",
1741 expected_type: GffType::UInt32,
1742 required: false,
1743 children: None,
1744 constraint: None,
1745 },
1746 FieldSchema {
1747 label: "DoBumping",
1748 expected_type: GffType::UInt8,
1749 required: false,
1750 children: None,
1751 constraint: None,
1752 },
1753 FieldSchema {
1754 label: "UseInertia",
1755 expected_type: GffType::UInt8,
1756 required: false,
1757 children: None,
1758 constraint: None,
1759 },
1760 FieldSchema {
1761 label: "DOF",
1762 expected_type: GffType::UInt32,
1763 required: false,
1764 children: None,
1765 constraint: None,
1766 },
1767 FieldSchema {
1768 label: "Music",
1769 expected_type: GffType::ResRef,
1770 required: false,
1771 children: None,
1772 constraint: None,
1773 },
1774 FieldSchema {
1775 label: "Far_Clip",
1776 expected_type: GffType::Single,
1777 required: false,
1778 children: None,
1779 constraint: None,
1780 },
1781 FieldSchema {
1782 label: "Near_Clip",
1783 expected_type: GffType::Single,
1784 required: false,
1785 children: None,
1786 constraint: None,
1787 },
1788 FieldSchema {
1789 label: "CameraViewAngle",
1790 expected_type: GffType::Single,
1791 required: false,
1792 children: None,
1793 constraint: None,
1794 },
1795 FieldSchema {
1796 label: "Player",
1797 expected_type: GffType::Struct,
1798 required: false,
1799 children: None,
1800 constraint: None,
1801 },
1802];
1803
1804impl GffSchema for Are {
1805 fn schema() -> &'static [FieldSchema] {
1806 static SCHEMA: &[FieldSchema] = &[
1807 FieldSchema {
1809 label: "ID",
1810 expected_type: GffType::Int32,
1811 required: false,
1812 children: None,
1813 constraint: None,
1814 },
1815 FieldSchema {
1816 label: "Creator_ID",
1817 expected_type: GffType::Int32,
1818 required: false,
1819 children: None,
1820 constraint: None,
1821 },
1822 FieldSchema {
1823 label: "Version",
1824 expected_type: GffType::UInt32,
1825 required: false,
1826 children: None,
1827 constraint: None,
1828 },
1829 FieldSchema {
1830 label: "Comments",
1831 expected_type: GffType::String,
1832 required: false,
1833 children: None,
1834 constraint: None,
1835 },
1836 FieldSchema {
1837 label: "Name",
1838 expected_type: GffType::LocalizedString,
1839 required: false,
1840 children: None,
1841 constraint: None,
1842 },
1843 FieldSchema {
1844 label: "Tag",
1845 expected_type: GffType::String,
1846 required: false,
1847 children: None,
1848 constraint: None,
1849 },
1850 FieldSchema {
1852 label: "OnHeartbeat",
1853 expected_type: GffType::ResRef,
1854 required: false,
1855 children: None,
1856 constraint: None,
1857 },
1858 FieldSchema {
1859 label: "OnUserDefined",
1860 expected_type: GffType::ResRef,
1861 required: false,
1862 children: None,
1863 constraint: None,
1864 },
1865 FieldSchema {
1866 label: "OnEnter",
1867 expected_type: GffType::ResRef,
1868 required: false,
1869 children: None,
1870 constraint: None,
1871 },
1872 FieldSchema {
1873 label: "OnExit",
1874 expected_type: GffType::ResRef,
1875 required: false,
1876 children: None,
1877 constraint: None,
1878 },
1879 FieldSchema {
1881 label: "Flags",
1882 expected_type: GffType::UInt32,
1883 required: false,
1884 children: None,
1885 constraint: None,
1886 },
1887 FieldSchema {
1888 label: "CameraStyle",
1889 expected_type: GffType::Int32,
1890 required: false,
1891 children: None,
1892 constraint: None,
1893 },
1894 FieldSchema {
1895 label: "DefaultEnvMap",
1896 expected_type: GffType::ResRef,
1897 required: false,
1898 children: None,
1899 constraint: None,
1900 },
1901 FieldSchema {
1902 label: "Unescapable",
1903 expected_type: GffType::UInt8,
1904 required: false,
1905 children: None,
1906 constraint: None,
1907 },
1908 FieldSchema {
1909 label: "RestrictMode",
1910 expected_type: GffType::UInt8,
1911 required: false,
1912 children: None,
1913 constraint: None,
1914 },
1915 FieldSchema {
1917 label: "ChanceRain",
1918 expected_type: GffType::Int32,
1919 required: false,
1920 children: None,
1921 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1922 },
1923 FieldSchema {
1924 label: "ChanceSnow",
1925 expected_type: GffType::Int32,
1926 required: false,
1927 children: None,
1928 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1929 },
1930 FieldSchema {
1931 label: "ChanceLightning",
1932 expected_type: GffType::Int32,
1933 required: false,
1934 children: None,
1935 constraint: Some(FieldConstraint::RangeInt(0, 100)),
1936 },
1937 FieldSchema {
1938 label: "WindPower",
1939 expected_type: GffType::Int32,
1940 required: false,
1941 children: None,
1942 constraint: Some(FieldConstraint::RangeInt(0, 2)),
1943 },
1944 FieldSchema {
1945 label: "ChanceFog",
1946 expected_type: GffType::Int32,
1947 required: false,
1948 children: None,
1949 constraint: None,
1950 },
1951 FieldSchema {
1953 label: "MoonAmbientColor",
1954 expected_type: GffType::UInt32,
1955 required: false,
1956 children: None,
1957 constraint: None,
1958 },
1959 FieldSchema {
1960 label: "MoonDiffuseColor",
1961 expected_type: GffType::UInt32,
1962 required: false,
1963 children: None,
1964 constraint: None,
1965 },
1966 FieldSchema {
1967 label: "MoonFogColor",
1968 expected_type: GffType::UInt32,
1969 required: false,
1970 children: None,
1971 constraint: None,
1972 },
1973 FieldSchema {
1974 label: "SunAmbientColor",
1975 expected_type: GffType::UInt32,
1976 required: false,
1977 children: None,
1978 constraint: None,
1979 },
1980 FieldSchema {
1981 label: "SunDiffuseColor",
1982 expected_type: GffType::UInt32,
1983 required: false,
1984 children: None,
1985 constraint: None,
1986 },
1987 FieldSchema {
1988 label: "SunFogColor",
1989 expected_type: GffType::UInt32,
1990 required: false,
1991 children: None,
1992 constraint: None,
1993 },
1994 FieldSchema {
1995 label: "DynAmbientColor",
1996 expected_type: GffType::UInt32,
1997 required: false,
1998 children: None,
1999 constraint: None,
2000 },
2001 FieldSchema {
2002 label: "MoonFogNear",
2003 expected_type: GffType::Single,
2004 required: false,
2005 children: None,
2006 constraint: None,
2007 },
2008 FieldSchema {
2009 label: "MoonFogFar",
2010 expected_type: GffType::Single,
2011 required: false,
2012 children: None,
2013 constraint: None,
2014 },
2015 FieldSchema {
2016 label: "SunFogNear",
2017 expected_type: GffType::Single,
2018 required: false,
2019 children: None,
2020 constraint: None,
2021 },
2022 FieldSchema {
2023 label: "SunFogFar",
2024 expected_type: GffType::Single,
2025 required: false,
2026 children: None,
2027 constraint: None,
2028 },
2029 FieldSchema {
2030 label: "MoonFogOn",
2031 expected_type: GffType::UInt8,
2032 required: false,
2033 children: None,
2034 constraint: None,
2035 },
2036 FieldSchema {
2037 label: "SunFogOn",
2038 expected_type: GffType::UInt8,
2039 required: false,
2040 children: None,
2041 constraint: None,
2042 },
2043 FieldSchema {
2044 label: "MoonShadows",
2045 expected_type: GffType::UInt8,
2046 required: false,
2047 children: None,
2048 constraint: None,
2049 },
2050 FieldSchema {
2051 label: "SunShadows",
2052 expected_type: GffType::UInt8,
2053 required: false,
2054 children: None,
2055 constraint: None,
2056 },
2057 FieldSchema {
2058 label: "DayNightCycle",
2059 expected_type: GffType::UInt8,
2060 required: false,
2061 children: None,
2062 constraint: None,
2063 },
2064 FieldSchema {
2065 label: "IsNight",
2066 expected_type: GffType::UInt8,
2067 required: false,
2068 children: None,
2069 constraint: None,
2070 },
2071 FieldSchema {
2072 label: "ShadowOpacity",
2073 expected_type: GffType::UInt8,
2074 required: false,
2075 children: None,
2076 constraint: None,
2077 },
2078 FieldSchema {
2079 label: "LightingScheme",
2080 expected_type: GffType::UInt8,
2081 required: false,
2082 children: None,
2083 constraint: None,
2084 },
2085 FieldSchema {
2086 label: "NoRest",
2087 expected_type: GffType::UInt8,
2088 required: false,
2089 children: None,
2090 constraint: None,
2091 },
2092 FieldSchema {
2094 label: "ModSpotCheck",
2095 expected_type: GffType::Int32,
2096 required: false,
2097 children: None,
2098 constraint: None,
2099 },
2100 FieldSchema {
2101 label: "ModListenCheck",
2102 expected_type: GffType::Int32,
2103 required: false,
2104 children: None,
2105 constraint: None,
2106 },
2107 FieldSchema {
2109 label: "Grass_Diffuse",
2110 expected_type: GffType::UInt32,
2111 required: false,
2112 children: None,
2113 constraint: None,
2114 },
2115 FieldSchema {
2116 label: "Grass_Ambient",
2117 expected_type: GffType::UInt32,
2118 required: false,
2119 children: None,
2120 constraint: None,
2121 },
2122 FieldSchema {
2123 label: "Grass_Density",
2124 expected_type: GffType::Single,
2125 required: false,
2126 children: None,
2127 constraint: None,
2128 },
2129 FieldSchema {
2130 label: "Grass_QuadSize",
2131 expected_type: GffType::Single,
2132 required: false,
2133 children: None,
2134 constraint: None,
2135 },
2136 FieldSchema {
2137 label: "Grass_TexName",
2138 expected_type: GffType::ResRef,
2139 required: false,
2140 children: None,
2141 constraint: None,
2142 },
2143 FieldSchema {
2144 label: "Grass_Prob_LL",
2145 expected_type: GffType::Single,
2146 required: false,
2147 children: None,
2148 constraint: None,
2149 },
2150 FieldSchema {
2151 label: "Grass_Prob_LR",
2152 expected_type: GffType::Single,
2153 required: false,
2154 children: None,
2155 constraint: None,
2156 },
2157 FieldSchema {
2158 label: "Grass_Prob_UL",
2159 expected_type: GffType::Single,
2160 required: false,
2161 children: None,
2162 constraint: None,
2163 },
2164 FieldSchema {
2165 label: "Grass_Prob_UR",
2166 expected_type: GffType::Single,
2167 required: false,
2168 children: None,
2169 constraint: None,
2170 },
2171 FieldSchema {
2172 label: "AlphaTest",
2173 expected_type: GffType::Single,
2174 required: false,
2175 children: None,
2176 constraint: None,
2177 },
2178 FieldSchema {
2180 label: "StealthXPMax",
2181 expected_type: GffType::UInt32,
2182 required: false,
2183 children: None,
2184 constraint: None,
2185 },
2186 FieldSchema {
2187 label: "StealthXPCurrent",
2188 expected_type: GffType::UInt32,
2189 required: false,
2190 children: None,
2191 constraint: None,
2192 },
2193 FieldSchema {
2194 label: "StealthXPLoss",
2195 expected_type: GffType::UInt32,
2196 required: false,
2197 children: None,
2198 constraint: None,
2199 },
2200 FieldSchema {
2201 label: "StealthXPEnabled",
2202 expected_type: GffType::UInt8,
2203 required: false,
2204 children: None,
2205 constraint: None,
2206 },
2207 FieldSchema {
2208 label: "TransPending",
2209 expected_type: GffType::UInt8,
2210 required: false,
2211 children: None,
2212 constraint: None,
2213 },
2214 FieldSchema {
2215 label: "TransPendNextID",
2216 expected_type: GffType::UInt8,
2217 required: false,
2218 children: None,
2219 constraint: None,
2220 },
2221 FieldSchema {
2222 label: "TransPendCurrID",
2223 expected_type: GffType::UInt8,
2224 required: false,
2225 children: None,
2226 constraint: None,
2227 },
2228 FieldSchema {
2230 label: "LoadScreenID",
2231 expected_type: GffType::UInt16,
2232 required: false,
2233 children: None,
2234 constraint: None,
2235 },
2236 FieldSchema {
2238 label: "Rooms",
2239 expected_type: GffType::List,
2240 required: false,
2241 children: Some(ROOMS_CHILDREN),
2242 constraint: None,
2243 },
2244 FieldSchema {
2245 label: "Expansion_List",
2246 expected_type: GffType::List,
2247 required: false,
2248 children: Some(EXPANSION_LIST_CHILDREN),
2249 constraint: None,
2250 },
2251 FieldSchema {
2252 label: "Map",
2253 expected_type: GffType::Struct,
2254 required: false,
2255 children: Some(MAP_CHILDREN),
2256 constraint: None,
2257 },
2258 FieldSchema {
2259 label: "MiniGame",
2260 expected_type: GffType::Struct,
2261 required: false,
2262 children: Some(MINI_GAME_CHILDREN),
2263 constraint: None,
2264 },
2265 FieldSchema {
2267 label: "DisableTransit",
2268 expected_type: GffType::UInt8,
2269 required: false,
2270 children: None,
2271 constraint: None,
2272 },
2273 FieldSchema {
2274 label: "NoHangBack",
2275 expected_type: GffType::UInt8,
2276 required: false,
2277 children: None,
2278 constraint: None,
2279 },
2280 FieldSchema {
2281 label: "PlayerOnly",
2282 expected_type: GffType::UInt8,
2283 required: false,
2284 children: None,
2285 constraint: None,
2286 },
2287 FieldSchema {
2288 label: "PlayerVsPlayer",
2289 expected_type: GffType::UInt8,
2290 required: false,
2291 children: None,
2292 constraint: None,
2293 },
2294 FieldSchema {
2295 label: "Grass_Emissive",
2296 expected_type: GffType::UInt32,
2297 required: false,
2298 children: None,
2299 constraint: None,
2300 },
2301 FieldSchema {
2303 label: "DirtyARGBOne",
2304 expected_type: GffType::Int32,
2305 required: false,
2306 children: None,
2307 constraint: None,
2308 },
2309 FieldSchema {
2310 label: "DirtySizeOne",
2311 expected_type: GffType::Int32,
2312 required: false,
2313 children: None,
2314 constraint: None,
2315 },
2316 FieldSchema {
2317 label: "DirtyFormulaOne",
2318 expected_type: GffType::Int32,
2319 required: false,
2320 children: None,
2321 constraint: None,
2322 },
2323 FieldSchema {
2324 label: "DirtyFuncOne",
2325 expected_type: GffType::Int32,
2326 required: false,
2327 children: None,
2328 constraint: None,
2329 },
2330 FieldSchema {
2331 label: "DirtyARGBTwo",
2332 expected_type: GffType::Int32,
2333 required: false,
2334 children: None,
2335 constraint: None,
2336 },
2337 FieldSchema {
2338 label: "DirtySizeTwo",
2339 expected_type: GffType::Int32,
2340 required: false,
2341 children: None,
2342 constraint: None,
2343 },
2344 FieldSchema {
2345 label: "DirtyFormulaTwo",
2346 expected_type: GffType::Int32,
2347 required: false,
2348 children: None,
2349 constraint: None,
2350 },
2351 FieldSchema {
2352 label: "DirtyFuncTwo",
2353 expected_type: GffType::Int32,
2354 required: false,
2355 children: None,
2356 constraint: None,
2357 },
2358 FieldSchema {
2359 label: "DirtyARGBThree",
2360 expected_type: GffType::Int32,
2361 required: false,
2362 children: None,
2363 constraint: None,
2364 },
2365 FieldSchema {
2366 label: "DirtySizeThree",
2367 expected_type: GffType::Int32,
2368 required: false,
2369 children: None,
2370 constraint: None,
2371 },
2372 FieldSchema {
2373 label: "DirtyFormulaThre",
2374 expected_type: GffType::Int32,
2375 required: false,
2376 children: None,
2377 constraint: None,
2378 },
2379 FieldSchema {
2380 label: "DirtyFuncThree",
2381 expected_type: GffType::Int32,
2382 required: false,
2383 children: None,
2384 constraint: None,
2385 },
2386 ];
2387 SCHEMA
2388 }
2389}
2390
2391#[cfg(test)]
2392mod tests {
2393 use super::*;
2394
2395 const TEST_ARE: &[u8] = include_bytes!(concat!(
2396 env!("CARGO_MANIFEST_DIR"),
2397 "/../../fixtures/test.are"
2398 ));
2399
2400 #[test]
2401 fn reads_core_are_fields_from_fixture() {
2402 let are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2403
2404 assert_eq!(are.unused_id, 0);
2405 assert_eq!(are.creator_id, 0);
2406 assert_eq!(are.tag, "Untitled");
2407 assert_eq!(are.name.string_ref.raw(), 75_101);
2408 assert_eq!(are.comment, "comments");
2409 assert_eq!(are.version, 88);
2410 assert_eq!(are.flags, 0);
2411 assert_eq!(are.mod_spot_check, 0);
2412 assert_eq!(are.mod_listen_check, 0);
2413 assert_eq!(are.camera_style, 1);
2414 assert_eq!(are.default_envmap, "defaultenvmap");
2415 assert_eq!(are.grass_texture, "grasstexture");
2416 assert!((are.grass_density - 1.0).abs() < f32::EPSILON);
2417 assert!((are.grass_size - 1.0).abs() < f32::EPSILON);
2418 assert!((are.grass_prob_ll - 0.25).abs() < f32::EPSILON);
2419 assert!((are.grass_prob_lr - 0.25).abs() < f32::EPSILON);
2420 assert!((are.grass_prob_ul - 0.25).abs() < f32::EPSILON);
2421 assert!((are.grass_prob_ur - 0.25).abs() < f32::EPSILON);
2422 assert_eq!(are.sun_ambient_color, 16_777_215);
2423 assert_eq!(are.sun_diffuse_color, 16_777_215);
2424 assert_eq!(are.dynamic_ambient_color, 16_777_215);
2425 assert_eq!(are.sun_fog_color, 16_777_215);
2426 assert_eq!(are.grass_ambient_color, 16_777_215);
2427 assert_eq!(are.grass_diffuse_color, 16_777_215);
2428 assert_eq!(are.grass_emissive_color, 16_777_215);
2429 assert!(are.fog_enabled);
2430 assert!((are.fog_near - 99.0).abs() < f32::EPSILON);
2431 assert!((are.fog_far - 100.0).abs() < f32::EPSILON);
2432 assert!(are.shadows);
2433 assert_eq!(are.shadow_opacity, 205);
2434 assert_eq!(are.wind_power, 1);
2435 assert!(are.unescapable);
2436 assert!(are.disable_transit);
2437 assert!(are.stealth_xp);
2438 assert_eq!(are.stealth_xp_loss, 25);
2439 assert_eq!(are.stealth_xp_max, 25);
2440 assert_eq!(are.on_enter, "k_on_enter");
2441 assert_eq!(are.on_exit, "onexit");
2442 assert_eq!(are.on_heartbeat, "onheartbeat");
2443 assert_eq!(are.on_user_defined, "onuserdefined");
2444 assert!((are.alpha_test - 0.2).abs() < f32::EPSILON);
2445 assert_eq!(are.chance_rain, 99);
2446 assert_eq!(are.chance_snow, 99);
2447 assert_eq!(are.chance_lightning, 99);
2448 assert_eq!(are.moon_ambient_color, 0);
2449 assert_eq!(are.moon_diffuse_color, 0);
2450 assert!(!are.moon_fog_enabled);
2451 assert!((are.moon_fog_near - 99.0).abs() < f32::EPSILON);
2452 assert!((are.moon_fog_far - 100.0).abs() < f32::EPSILON);
2453 assert_eq!(are.moon_fog_color, 0);
2454 assert!(!are.moon_shadows);
2455 assert_eq!(are.dirty_argb_one, 123);
2456 assert_eq!(are.dirty_size_one, 1);
2457 assert_eq!(are.dirty_formula_one, 1);
2458 assert_eq!(are.dirty_func_one, 1);
2459 assert_eq!(are.dirty_argb_two, 1234);
2460 assert_eq!(are.dirty_size_two, 1);
2461 assert_eq!(are.dirty_formula_two, 1);
2462 assert_eq!(are.dirty_func_two, 1);
2463 assert_eq!(are.dirty_argb_three, 12_345);
2464 assert_eq!(are.dirty_size_three, 1);
2465 assert_eq!(are.dirty_formula_three, 1);
2466 assert_eq!(are.dirty_func_three, 1);
2467 assert!(!are.is_night);
2468 assert_eq!(are.lighting_scheme, 0);
2469 assert_eq!(are.day_night_cycle, 0);
2470 assert!(!are.no_rest);
2471 assert!(!are.no_hang_back);
2472 assert!(!are.player_only);
2473 assert_eq!(are.player_vs_player, 3);
2474 assert_eq!(are.map.map_zoom, 1);
2475 assert_eq!(are.map.map_res_x, 18);
2476 assert_eq!(are.rooms.len(), 2);
2477 assert!(are.expansion_list.is_empty());
2478 assert_eq!(are.rooms[0].room_name, "002ebo");
2479 assert!(are.rooms[0].disable_weather);
2480 assert!(are.rooms[0].part_sounds.is_empty());
2481 }
2482
2483 #[test]
2484 fn all_fields_survive_typed_roundtrip() {
2485 let are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2486 let encoded = write_are_to_vec(&are).expect("encode must succeed");
2487 let reparsed = read_are_from_bytes(&encoded).expect("decode must succeed");
2488 assert_eq!(are, reparsed);
2489 }
2490
2491 #[test]
2492 fn typed_edits_roundtrip_through_gff_writer() {
2493 let mut are = read_are_from_bytes(TEST_ARE).expect("fixture must parse");
2494 are.tag = "m01aa".into();
2495 are.on_enter = ResRef::new("k_on_newenter").expect("valid test resref");
2496 are.grass_texture = ResRef::new("new_grass").expect("valid test resref");
2497 are.fog_enabled = false;
2498 are.fog_near = 50.0;
2499 are.shadow_opacity = 180;
2500 are.stealth_xp_loss = 33;
2501 are.dirty_formula_three = 9;
2502 are.player_vs_player = 2;
2503 are.restrict_mode = 1;
2504 are.chance_fog = 42;
2505 are.stealth_xp_current = 10;
2506 are.trans_pending = 1;
2507 are.trans_pend_next_id = 3;
2508 are.trans_pend_curr_id = 2;
2509 are.map.map_zoom = 7;
2510 are.rooms[0].ambient_scale = 0.5;
2511 are.expansion_list.push(AreExpansionEntry {
2512 expansion_name: GffLocalizedString::new(StrRef::from_raw(321)),
2513 expansion_id: 42,
2514 });
2515 are.rooms[0].part_sounds.push(ArePartSound {
2516 looping: true,
2517 model_part: "ROOM_A".into(),
2518 omen_event: "ON_ENTER".into(),
2519 sound: ResRef::new("amb_rooma").expect("valid test resref"),
2520 });
2521
2522 let encoded = write_are_to_vec(&are).expect("encode");
2523 let reparsed = read_are_from_bytes(&encoded).expect("decode");
2524
2525 assert_eq!(reparsed.tag, "m01aa");
2526 assert_eq!(reparsed.on_enter, "k_on_newenter");
2527 assert_eq!(reparsed.grass_texture, "new_grass");
2528 assert!(!reparsed.fog_enabled);
2529 assert!((reparsed.fog_near - 50.0).abs() < f32::EPSILON);
2530 assert_eq!(reparsed.shadow_opacity, 180);
2531 assert_eq!(reparsed.stealth_xp_loss, 33);
2532 assert_eq!(reparsed.dirty_formula_three, 9);
2533 assert_eq!(reparsed.player_vs_player, 2);
2534 assert_eq!(reparsed.restrict_mode, 1);
2535 assert_eq!(reparsed.chance_fog, 42);
2536 assert_eq!(reparsed.stealth_xp_current, 10);
2537 assert_eq!(reparsed.trans_pending, 1);
2538 assert_eq!(reparsed.trans_pend_next_id, 3);
2539 assert_eq!(reparsed.trans_pend_curr_id, 2);
2540 assert_eq!(reparsed.map.map_zoom, 7);
2541 assert_eq!(reparsed.rooms[0].ambient_scale, 0.5);
2542 assert_eq!(reparsed.expansion_list.len(), 1);
2543 assert_eq!(reparsed.expansion_list[0].expansion_id, 42);
2544 assert_eq!(
2545 reparsed.expansion_list[0].expansion_name.string_ref.raw(),
2546 321
2547 );
2548 assert_eq!(reparsed.rooms[0].part_sounds.len(), 1);
2549 assert!(reparsed.rooms[0].part_sounds[0].looping);
2550 assert_eq!(reparsed.rooms[0].part_sounds[0].model_part, "ROOM_A");
2551 assert_eq!(reparsed.rooms[0].part_sounds[0].omen_event, "ON_ENTER");
2552 assert_eq!(reparsed.rooms[0].part_sounds[0].sound, "amb_rooma");
2553 assert_eq!(reparsed.rooms.len(), 2);
2554 }
2555
2556 #[test]
2557 fn rejects_non_are_file_type() {
2558 let gff = Gff::new(*b"DLG ", GffStruct::new(-1));
2559 let err = Are::from_gff(&gff).expect_err("must fail");
2560 assert!(matches!(err, AreError::UnsupportedFileType(file_type) if file_type == *b"DLG "));
2561 }
2562
2563 #[test]
2564 fn read_are_from_reader_matches_bytes_path() {
2565 let mut cursor = Cursor::new(TEST_ARE);
2566 let via_reader = read_are(&mut cursor).expect("reader parse");
2567 let via_bytes = read_are_from_bytes(TEST_ARE).expect("bytes parse");
2568 assert_eq!(via_reader.tag, via_bytes.tag);
2569 assert_eq!(via_reader.rooms.len(), via_bytes.rooms.len());
2570 }
2571
2572 #[test]
2573 fn type_mismatch_on_rooms_field_is_error() {
2574 let mut root = GffStruct::new(-1);
2575 root.push_field("Rooms", GffValue::UInt32(7));
2576 let gff = Gff::new(*b"ARE ", root);
2577 let err = Are::from_gff(&gff).expect_err("must fail");
2578 assert!(matches!(
2579 err,
2580 AreError::TypeMismatch {
2581 field: "Rooms",
2582 expected: "List"
2583 }
2584 ));
2585 }
2586
2587 #[test]
2588 fn map_points_accept_int_encoding_for_k1_parity() {
2589 let mut root = GffStruct::new(-1);
2590 let mut map = GffStruct::new(0);
2591 map.push_field("MapPt1X", GffValue::Int32(10));
2592 map.push_field("MapPt1Y", GffValue::Int32(20));
2593 map.push_field("MapPt2X", GffValue::UInt16(30));
2594 map.push_field("MapPt2Y", GffValue::UInt8(40));
2595 root.push_field("Map", GffValue::Struct(Box::new(map)));
2596 let gff = Gff::new(*b"ARE ", root);
2597
2598 let are = Are::from_gff(&gff).expect("must parse");
2599 assert_eq!(are.map.map_point_1, [10.0, 20.0]);
2600 assert_eq!(are.map.map_point_2, [30.0, 40.0]);
2601 }
2602
2603 #[test]
2604 fn type_mismatch_on_expansion_list_field_is_error() {
2605 let mut root = GffStruct::new(-1);
2606 root.push_field("Expansion_List", GffValue::UInt32(7));
2607 let gff = Gff::new(*b"ARE ", root);
2608 let err = Are::from_gff(&gff).expect_err("must fail");
2609 assert!(matches!(
2610 err,
2611 AreError::TypeMismatch {
2612 field: "Expansion_List",
2613 expected: "List"
2614 }
2615 ));
2616 }
2617
2618 #[test]
2619 fn type_mismatch_on_room_part_sounds_field_is_error() {
2620 let mut root = GffStruct::new(-1);
2621 let mut room = GffStruct::new(0);
2622 room.push_field("PartSounds", GffValue::UInt32(7));
2623 root.push_field("Rooms", GffValue::List(vec![room]));
2624 let gff = Gff::new(*b"ARE ", root);
2625 let err = Are::from_gff(&gff).expect_err("must fail");
2626 assert!(matches!(
2627 err,
2628 AreError::TypeMismatch {
2629 field: "Rooms[].PartSounds",
2630 expected: "List"
2631 }
2632 ));
2633 }
2634
2635 #[test]
2636 fn type_mismatch_on_part_sound_looping_field_is_error() {
2637 let mut root = GffStruct::new(-1);
2638 let mut room = GffStruct::new(0);
2639 let mut part_sound = GffStruct::new(0);
2640 part_sound.push_field("Looping", GffValue::String("yes".into()));
2641 room.push_field("PartSounds", GffValue::List(vec![part_sound]));
2642 root.push_field("Rooms", GffValue::List(vec![room]));
2643 let gff = Gff::new(*b"ARE ", root);
2644 let err = Are::from_gff(&gff).expect_err("must fail");
2645 assert!(matches!(
2646 err,
2647 AreError::TypeMismatch {
2648 field: "Rooms[].PartSounds[].Looping",
2649 expected: "numeric bool"
2650 }
2651 ));
2652 }
2653
2654 #[test]
2655 fn write_are_matches_direct_gff_writer() {
2656 let are = read_are_from_bytes(TEST_ARE).expect("fixture parse");
2657 let from_are = write_are_to_vec(&are).expect("are encode");
2658
2659 let gff = are.to_gff();
2660 let from_gff = rakata_formats::write_gff_to_vec(&gff).expect("gff encode");
2661 assert_eq!(from_are, from_gff);
2662 }
2663
2664 #[test]
2665 fn schema_field_count() {
2666 assert_eq!(Are::schema().len(), 81);
2667 }
2668
2669 #[test]
2670 fn schema_no_duplicate_labels() {
2671 let schema = Are::schema();
2672 let mut labels: Vec<&str> = schema.iter().map(|f| f.label).collect();
2673 labels.sort();
2674 let before = labels.len();
2675 labels.dedup();
2676 assert_eq!(before, labels.len(), "duplicate labels in ARE schema");
2677 }
2678
2679 #[test]
2680 fn schema_rooms_has_children() {
2681 let rooms = Are::schema()
2682 .iter()
2683 .find(|f| f.label == "Rooms")
2684 .expect("Rooms field must exist in schema");
2685 assert!(rooms.children.is_some());
2686 assert_eq!(
2687 rooms
2688 .children
2689 .expect("Rooms children must be present")
2690 .len(),
2691 5
2692 );
2693 }
2694
2695 #[test]
2696 fn schema_map_has_children() {
2697 let map = Are::schema()
2698 .iter()
2699 .find(|f| f.label == "Map")
2700 .expect("Map field must exist in schema");
2701 assert!(map.children.is_some());
2702 assert_eq!(
2703 map.children.expect("Map children must be present").len(),
2704 11
2705 );
2706 }
2707
2708 #[test]
2709 fn type_mismatch_on_mini_game_field_is_error() {
2710 let mut root = GffStruct::new(-1);
2711 root.push_field("MiniGame", GffValue::UInt32(7));
2712 let gff = Gff::new(*b"ARE ", root);
2713 let err = Are::from_gff(&gff).expect_err("must fail");
2714 assert!(matches!(
2715 err,
2716 AreError::TypeMismatch {
2717 field: "MiniGame",
2718 expected: "Struct"
2719 }
2720 ));
2721 }
2722
2723 #[test]
2724 fn mini_game_struct_roundtrips() {
2725 let mg = AreMiniGame {
2726 mini_game_type: 1,
2727 movement_per_sec: 25.0,
2728 lateral_accel: 45.0,
2729 bump_plane: 2,
2730 do_bumping: true,
2731 use_inertia: true,
2732 dof: 3,
2733 music: ResRef::new("mus_swoop").expect("valid test resref"),
2734 far_clip: 200.0,
2735 near_clip: 0.5,
2736 camera_view_angle: 70.0,
2737 player: Some(AreMiniGamePlayer {
2738 models: vec![AreMiniGameModel {
2739 model: ResRef::new("swoopbike").expect("valid test resref"),
2740 rotating_model: false,
2741 }],
2742 track: ResRef::new("trk_race01").expect("valid test resref"),
2743 camera: ResRef::new("cam_swoop").expect("valid test resref"),
2744 camera_rotate: true,
2745 mouse: AreMiniGameMouse {
2746 axis_x: 1,
2747 axis_y: 2,
2748 flip_axis_x: true,
2749 flip_axis_y: false,
2750 },
2751 enemies: vec![AreMiniGameEnemy {
2752 models: Vec::new(),
2753 track: ResRef::new("trk_enemy01").expect("valid test resref"),
2754 }],
2755 obstacles: vec![AreMiniGameObstacle {
2756 name: ResRef::new("obs_rock").expect("valid test resref"),
2757 }],
2758 }),
2759 };
2760
2761 let mut are = Are::new();
2762 are.mini_game = Some(mg);
2763
2764 let encoded = write_are_to_vec(&are).expect("encode");
2765 let reparsed = read_are_from_bytes(&encoded).expect("decode");
2766
2767 let mg_out = reparsed.mini_game.expect("MiniGame must survive roundtrip");
2768 assert_eq!(mg_out.mini_game_type, 1);
2769 assert!((mg_out.movement_per_sec - 25.0).abs() < f32::EPSILON);
2770 assert!((mg_out.lateral_accel - 45.0).abs() < f32::EPSILON);
2771 assert_eq!(mg_out.bump_plane, 2);
2772 assert!(mg_out.do_bumping);
2773 assert!(mg_out.use_inertia);
2774 assert_eq!(mg_out.dof, 3);
2775 assert_eq!(mg_out.music, "mus_swoop");
2776 assert!((mg_out.far_clip - 200.0).abs() < f32::EPSILON);
2777 assert!((mg_out.near_clip - 0.5).abs() < f32::EPSILON);
2778 assert!((mg_out.camera_view_angle - 70.0).abs() < f32::EPSILON);
2779
2780 let player = mg_out.player.expect("Player must survive roundtrip");
2781 assert_eq!(player.models.len(), 1);
2782 assert_eq!(player.models[0].model, "swoopbike");
2783 assert!(!player.models[0].rotating_model);
2784 assert_eq!(player.track, "trk_race01");
2785 assert_eq!(player.camera, "cam_swoop");
2786 assert!(player.camera_rotate);
2787 assert_eq!(player.mouse.axis_x, 1);
2788 assert_eq!(player.mouse.axis_y, 2);
2789 assert!(player.mouse.flip_axis_x);
2790 assert!(!player.mouse.flip_axis_y);
2791 assert_eq!(player.enemies.len(), 1);
2792 assert!(player.enemies[0].models.is_empty());
2793 assert_eq!(player.enemies[0].track, "trk_enemy01");
2794 assert_eq!(player.obstacles.len(), 1);
2795 assert_eq!(player.obstacles[0].name, "obs_rock");
2796 }
2797}