Skip to main content

rpfm_lib/files/bmd_vegetation/
mod.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! BMD Vegetation file format support.
12//!
13//! BMD Vegetation files (`.vegetation`) are FASTBIN0-format files that define vegetation
14//! placement for battle maps in Total War games. They contain tree and grass placement
15//! data that complements the main BMD file.
16//!
17//! # File Format
18//!
19//! BMD Vegetation files use the FASTBIN0 binary format:
20//!
21//! ```text
22//! [8 bytes]  FASTBIN0 signature
23//! [u16]      serialise_version
24//! [...]      version-specific data
25//! ```
26//!
27//! # Supported Versions
28//!
29//! - **Version 2**: Current format used in Warhammer III
30//!
31//! # File Contents
32//!
33//! - **Tree List**: Individual tree placements with species, position, scale, rotation
34//! - **Grass List**: Grass placement areas with density and appearance settings
35//!
36//! # Usage
37//!
38//! ```ignore
39//! use rpfm_lib::files::bmd_vegetation::BmdVegetation;
40//! use rpfm_lib::files::Decodeable;
41//!
42//! // Decode a vegetation file
43//! let vegetation = BmdVegetation::decode(&mut reader, &None)?;
44//!
45//! println!("Version: {}", vegetation.serialise_version());
46//! println!("Trees: {}", vegetation.tree_list().list().len());
47//! println!("Grass patches: {}", vegetation.grass_list().list().len());
48//! ```
49//!
50//! # Integration with BMD
51//!
52//! Vegetation files are referenced from BMD files via:
53//! - [`TreeListReferenceList`](crate::files::bmd::Bmd) - Links to tree placement data
54//! - [`GrassListReferenceList`](crate::files::bmd::Bmd) - Links to grass coverage data
55//!
56//! # Terry Export
57//!
58//! Vegetation data can be exported to Terry (CA's editor) format via the [`ToLayer`]
59//! trait implementation, which generates XML entity definitions for trees and grass.
60//!
61//! # File Location
62//!
63//! Vegetation files are typically found in:
64//! ```text
65//! terrain/battles/*.vegetation
66//! terrain/tiles/*.vegetation
67//! ```
68
69use getset::*;
70use serde_derive::{Serialize, Deserialize};
71
72use crate::binary::{ReadBytes, WriteBytes};
73use crate::error::{Result, RLibError};
74use crate::files::{Bmd, bmd::ToLayer, Decodeable, EncodeableExtraData, Encodeable};
75use crate::utils::check_size_mismatch;
76
77use self::grass_list::GrassList;
78use self::tree_list::TreeList;
79
80use super::DecodeableExtraData;
81
82/// File extension for BMD Vegetation files.
83///
84/// BMD Vegetation files use the `.vegetation` extension.
85pub const EXTENSIONS: [&str; 1] = [
86    ".vegetation",
87];
88
89/// FASTBIN0 file signature.
90///
91/// All BMD Vegetation files start with this 8-byte signature: `FASTBIN0`
92/// (bytes: `[0x46, 0x41, 0x53, 0x54, 0x42, 0x49, 0x4E, 0x30]`)
93pub const SIGNATURE: &[u8; 8] = &[0x46, 0x41, 0x53, 0x54, 0x42, 0x49, 0x4E, 0x30];
94
95mod grass_list;
96mod tree_list;
97mod v2;
98
99#[cfg(test)] mod bmd_vegetation_test;
100
101//---------------------------------------------------------------------------//
102//                              Enum & Structs
103//---------------------------------------------------------------------------//
104
105/// Represents a BMD Vegetation file decoded in memory.
106///
107/// This struct contains all vegetation placement data for a battle map, including
108/// individual tree instances and grass coverage areas. Vegetation data is stored
109/// separately from the main BMD file to allow independent editing.
110///
111/// # Fields
112///
113/// - `serialise_version`: File format version (currently only version 2)
114/// - `tree_list`: List of individual tree placements
115/// - `grass_list`: List of grass coverage areas
116///
117/// # Version Support
118///
119/// - **Version 2**: Current format used in Warhammer III
120///
121/// # Integration
122///
123/// Vegetation files work in conjunction with BMD files. The BMD file contains
124/// references to vegetation data via `TreeListReferenceList` and `GrassListReferenceList`,
125/// while this file contains the actual placement data.
126///
127/// # Example
128///
129/// ```ignore
130/// use rpfm_lib::files::bmd_vegetation::BmdVegetation;
131/// use rpfm_lib::files::Decodeable;
132///
133/// let vegetation = BmdVegetation::decode(&mut reader, &None)?;
134///
135/// // Access tree data
136/// for tree in vegetation.tree_list().list() {
137///     println!("Tree at ({}, {}, {})",
138///         tree.position().x(),
139///         tree.position().y(),
140///         tree.position().z()
141///     );
142/// }
143///
144/// // Access grass data
145/// for grass in vegetation.grass_list().list() {
146///     println!("Grass patch: {}", grass.grass_type());
147/// }
148/// ```
149#[derive(Default, PartialEq, Clone, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
150#[getset(get = "pub", get_mut = "pub", set = "pub")]
151pub struct BmdVegetation {
152    /// File format version number (currently only version 2).
153    serialise_version: u16,
154
155    /// List of individual tree placements.
156    tree_list: TreeList,
157
158    /// List of grass coverage areas.
159    grass_list: GrassList,
160}
161
162//---------------------------------------------------------------------------//
163//                           Implementation of BmdVegetation
164//---------------------------------------------------------------------------//
165
166impl Decodeable for BmdVegetation {
167
168    fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
169        let signature_bytes = data.read_slice(8, false)?;
170        if signature_bytes.as_slice() != SIGNATURE {
171            return Err(RLibError::DecodingFastBinUnsupportedSignature(signature_bytes));
172        }
173
174        let mut fastbin = Self::default();
175        fastbin.serialise_version = data.read_u16()?;
176
177        match fastbin.serialise_version {
178            2 => fastbin.read_v2(data, extra_data)?,
179            _ => return Err(RLibError::DecodingFastBinUnsupportedVersion(String::from("BmdVegetation"), fastbin.serialise_version)),
180        }
181
182        // If we are not in the last byte, it means we didn't parse the entire file, which means this file is corrupt.
183        check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
184
185        Ok(fastbin)
186    }
187}
188
189impl Encodeable for BmdVegetation {
190
191    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
192        buffer.write_all(SIGNATURE)?;
193        buffer.write_u16(self.serialise_version)?;
194
195        match self.serialise_version {
196            2 => self.write_v2(buffer, extra_data)?,
197            _ => return Err(RLibError::EncodingFastBinUnsupportedVersion(String::from("BmdVegetation"), self.serialise_version)),
198        }
199
200        Ok(())
201    }
202}
203
204
205impl ToLayer for BmdVegetation {
206    fn to_layer(&self, parent: &Bmd) -> Result<String> {
207        let mut layer = String::new();
208
209        layer.push_str(&self.tree_list().to_layer(parent)?);
210        //layer.push_str(self.grass_list().to_layer(parent)?);
211
212        Ok(layer)
213    }
214}