Skip to main content

rpfm_lib/files/anims_table/
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//! Animation table file format support.
12//!
13//! This module handles animation table files (`*_tables.bin`) which define animation
14//! sets and their associated fragments for unit skeletons in Total War games. These
15//! files act as indices that map skeleton types to their animation fragment files.
16//!
17//! # File Format
18//!
19//! Animation tables use a binary format (version 2) containing:
20//! - List of animation table entries
21//! - Each entry maps a skeleton type to animation fragments
22//! - Fragment references with metadata
23//!
24//! # File Naming Convention
25//!
26//! Animation table files must end with `_tables.bin` to be recognized by RPFM.
27//! This is a library-specific requirement for disambiguation, not a game limitation.
28//!
29//! Common naming patterns:
30//! - `humanoid01_tables.bin` - Human skeleton animations
31//! - `cavalry_tables.bin` - Mounted unit animations
32//! - `monster_tables.bin` - Large creature animations
33//!
34//! # File Organization
35//!
36//! Animation tables are stored in:
37//! ```text
38//! animations/animation_tables/{skeleton_type}_tables.bin
39//! ```
40//!
41//! # Structure
42//!
43//! Each table contains entries that define:
44//! - Animation table name (logical identifier)
45//! - Skeleton type (which skeleton this applies to)
46//! - Mount table reference (for mounted units)
47//! - List of animation fragments with metadata
48//!
49//! # Supported Versions
50//!
51//! Currently only version 2 is supported, used in modern Total War games
52//! (Warhammer 2, Three Kingdoms, Warhammer 3, etc.).
53//!
54//! # Usage
55//!
56//! ```ignore
57//! use rpfm_lib::files::anims_table::AnimsTable;
58//! use rpfm_lib::files::Decodeable;
59//!
60//! // Decode an animation table
61//! let table = AnimsTable::decode(&mut data, &None)?;
62//!
63//! // Access entries
64//! for entry in table.entries() {
65//!     println!("Table: {} for skeleton: {}",
66//!         entry.table_name(),
67//!         entry.skeleton_type()
68//!     );
69//!
70//!     // List fragments
71//!     for fragment in entry.fragments() {
72//!         println!("  Fragment: {}", fragment.name());
73//!     }
74//! }
75//! ```
76
77use getset::{Getters, Setters};
78use serde_derive::{Serialize, Deserialize};
79
80use crate::binary::{ReadBytes, WriteBytes};
81use crate::error::{RLibError, Result};
82use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
83use crate::utils::check_size_mismatch;
84
85/// Base directory path for animation files.
86pub const BASE_PATH: &str = "animations/";
87
88/// File extension pattern for animation table files.
89///
90/// To differentiate animation tables from other `.bin` files, RPFM only recognizes
91/// files ending in `_tables.bin` as AnimsTable files.
92///
93/// **Note**: This is a library-specific requirement for disambiguation, not a game
94/// limitation. The game itself doesn't require this specific naming pattern.
95pub const EXTENSION: &str = "_tables.bin";
96
97mod versions;
98
99#[cfg(test)] mod anims_table_test;
100
101//---------------------------------------------------------------------------//
102//                              Enum & Structs
103//---------------------------------------------------------------------------//
104
105/// Represents an animation table file (`*_tables.bin`).
106///
107/// Animation tables serve as indices mapping skeleton types to their available animation
108/// fragment files. Each table contains one or more entries defining animation sets for
109/// different skeleton configurations.
110///
111/// # Fields
112///
113/// - `version`: File format version (currently only version 2 is supported)
114/// - `entries`: List of animation table entries, each mapping a skeleton to fragments
115///
116/// # Version Support
117///
118/// - **Version 2**: Current format used in Warhammer 2, Three Kingdoms, Warhammer 3
119///
120/// # Example
121///
122/// ```ignore
123/// // Create a new animation table
124/// let mut table = AnimsTable::default();
125/// table.set_version(2);
126///
127/// // Add an entry for humanoid skeletons
128/// let mut entry = Entry::default();
129/// entry.set_table_name("humanoid01_animations".to_string());
130/// entry.set_skeleton_type("humanoid01".to_string());
131/// table.entries_mut().push(entry);
132/// ```
133#[derive(PartialEq, Clone, Debug, Default, Getters, Setters, Serialize, Deserialize)]
134#[getset(get = "pub", set = "pub")]
135pub struct AnimsTable {
136    /// File format version number.
137    ///
138    /// Only version 2 is currently supported.
139    version: u32,
140
141    /// List of animation table entries.
142    ///
143    /// Each entry defines animations for a specific skeleton type and configuration.
144    entries: Vec<Entry>,
145}
146
147/// Represents a single animation table entry mapping a skeleton to fragments.
148///
149/// Each entry defines an animation set for a specific skeleton type. Entries can
150/// reference mount tables for cavalry/mounted units and contain lists of animation
151/// fragments that apply to this skeleton configuration.
152///
153/// # Fields
154///
155/// - `table_name`: Logical name of this animation table (e.g., "humanoid01_animations")
156/// - `skeleton_type`: Skeleton identifier this entry applies to (e.g., "humanoid01")
157/// - `mount_table_name`: Reference to mount table for mounted units (empty for infantry)
158/// - `fragments`: List of animation fragment references
159/// - `uk_6`: Unknown boolean flag (purpose unclear)
160/// - `uk_7`: Unknown boolean flag (purpose unclear)
161///
162/// # Mount Tables
163///
164/// For mounted units (cavalry, chariots, monsters with riders), `mount_table_name`
165/// references another animation table that defines the mount's animations. Infantry
166/// units leave this field empty.
167///
168/// # Example
169///
170/// ```ignore
171/// // Infantry entry
172/// let mut infantry = Entry::default();
173/// infantry.set_table_name("hu1_animations".to_string());
174/// infantry.set_skeleton_type("humanoid01".to_string());
175/// infantry.set_mount_table_name(String::new());
176///
177/// // Cavalry entry
178/// let mut cavalry = Entry::default();
179/// cavalry.set_table_name("cav_animations".to_string());
180/// cavalry.set_skeleton_type("humanoid01".to_string());
181/// cavalry.set_mount_table_name("horse_animations".to_string());
182/// ```
183#[derive(PartialEq, Clone, Debug, Default, Getters, Setters, Serialize, Deserialize)]
184#[getset(get = "pub", set = "pub")]
185pub struct Entry {
186    /// Logical name of this animation table.
187    ///
188    /// Used as an identifier for this animation set (e.g., "humanoid01_animations").
189    ///
190    /// FIXME: This is possibly not table_name, but skeleton_type, and skeleton_type is possibly skeleton_type_cinematic.
191    table_name: String,
192
193    /// Skeleton type identifier.
194    ///
195    /// Specifies which skeleton this entry's animations apply to (e.g., "humanoid01",
196    /// "cavalry", "monster").
197    skeleton_type: String,
198
199    /// Mount table reference for mounted units.
200    ///
201    /// For cavalry/mounted units, this references the animation table for the mount
202    /// (e.g., "horse_animations"). Empty string for infantry/unmounted units.
203    mount_table_name: String,
204
205    /// List of animation fragments for this skeleton.
206    ///
207    /// Each fragment references an animation file and associated metadata.
208    fragments: Vec<Fragment>,
209
210    /// Unknown boolean flag.
211    ///
212    /// Purpose currently unclear. May be related to animation blending or state flags.
213    uk_6: bool,
214
215    /// Unknown boolean flag.
216    ///
217    /// Purpose currently unclear. May be related to animation blending or state flags.
218    uk_7: bool,
219}
220
221/// Represents a reference to an animation fragment file.
222///
223/// Fragments are individual animation files that can be applied to a skeleton.
224/// Each fragment reference includes the fragment's name and associated metadata.
225///
226/// # Fields
227///
228/// - `name`: Animation fragment file name (without path or extension)
229/// - `uk_5`: Unknown 32-bit metadata value (purpose unclear)
230///
231/// # Fragment Naming
232///
233/// Fragment names typically follow patterns like:
234/// - `hu1_walk_01` - Humanoid walking animation
235/// - `cav_charge_02` - Cavalry charge animation
236/// - `monster_attack_melee` - Monster melee attack
237///
238/// The actual fragment files are stored in separate `.bin` or `.frg` files in the
239/// animations directory structure.
240///
241/// # Example
242///
243/// ```ignore
244/// let mut fragment = Fragment::default();
245/// fragment.set_name("hu1_idle_breathing_01".to_string());
246/// fragment.set_uk_5(0);  // Unknown field
247/// ```
248#[derive(PartialEq, Clone, Debug, Default, Getters, Setters, Serialize, Deserialize)]
249#[getset(get = "pub", set = "pub")]
250pub struct Fragment {
251    /// Animation fragment file name.
252    ///
253    /// The name of the fragment file (without path or extension). For example,
254    /// "hu1_walk_01" refers to a humanoid walking animation fragment.
255    name: String,
256
257    /// Unknown 32-bit metadata value.
258    ///
259    /// Purpose currently unclear. May be related to fragment priority, blending
260    /// weight, or other animation system metadata.
261    uk_5: u32,
262}
263
264//---------------------------------------------------------------------------//
265//                      Implementation of AnimsTable
266//---------------------------------------------------------------------------//
267
268impl Decodeable for AnimsTable {
269
270    fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
271        let mut table = Self::default();
272        table.version = data.read_u32()?;
273
274        match table.version {
275            2 => table.read_v2(data)?,
276            _ => Err(RLibError::DecodingMatchedCombatUnsupportedVersion(table.version as usize))?,
277        }
278
279        // If we are not in the last byte, it means we didn't parse the entire file, which means this file is corrupt.
280        check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
281
282        Ok(table)
283    }
284}
285
286impl Encodeable for AnimsTable {
287
288    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
289        buffer.write_u32(self.version)?;
290
291        match self.version {
292            2 => self.write_v2(buffer)?,
293            _ => Err(RLibError::DecodingAnimFragmentUnsupportedVersion(self.version as usize))?,
294        };
295
296        Ok(())
297    }
298}