Skip to main content

banished_formats/game/
interface.rs

1//! Interface resources: the toolbar, the UI controller and status icons.
2
3use super::*;
4
5/// One entry of the in-game toolbar: a button or a sub-menu. The game builds each level of the
6/// toolbar from the entries whose `parent` matches, in the order of `order`.
7///
8/// Original: `FUN_1400d74e0`; the toolbar is built by `FUN_1400bede0`.
9#[derive(Debug, Clone)]
10pub struct ToolbarItem {
11    /// The string table.
12    pub string_table: Option<String>,
13    /// The icon's sprite sheet.
14    pub sprite_sheet: Option<String>,
15    /// Tooltip string key.
16    pub tooltip: String,
17    /// Icon sprite name in `sprite_sheet`.
18    pub sprite: String,
19    // +0x48
20    /// Action kind: 0 sub-menu, 1 build template, 6–10 time controls, …
21    pub kind: u32,
22    // +0x60
23    /// Parent toolbar, `None` for the top level.
24    pub parent: Option<String>,
25    // +0x58
26    /// Template placed by this entry, or the utility it opens.
27    pub template: Option<String>,
28    // +0x40
29    /// Not identified yet.
30    pub value: u32,
31    // +0x45
32    /// Listed in the toolbar.
33    pub visible: bool,
34    // +0x44
35    /// Not identified yet.
36    pub enabled: bool,
37    // +0x4c
38    /// Not identified yet.
39    pub id: i32,
40    // +0x50
41    /// Sort key within its parent.
42    pub order: i32,
43}
44
45/// Read a [`ToolbarItem`] from the bytes of its resource.
46pub fn toolbar_item(data: &[u8]) -> Result<ToolbarItem> {
47    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
48        if h.type_id != type_id("Toolbar") {
49            return format_err(format!("expected Toolbar, found {:#010x}", h.type_id));
50        }
51        r.block(|r, _| {
52            Ok(ToolbarItem {
53                string_table: r.external_ref()?,
54                sprite_sheet: r.external_ref()?,
55                tooltip: r.string()?,
56                sprite: r.string()?,
57                kind: r.u32()?,
58                parent: r.external_ref()?,
59                template: r.external_ref()?,
60                value: r.u32()?,
61                visible: r.bool()?,
62                enabled: r.bool()?,
63                id: r.i32()?,
64                order: r.i32()?,
65            })
66        })
67    })?;
68    Ok(value)
69}
70
71/// A template's toolbar component (`ToolbarDescription`): the icon, name and tooltip shown for
72/// build and utility entries, and its placement messages.
73///
74/// Original: `FUN_140132f00`.
75#[derive(Debug, Clone)]
76pub struct ToolbarDescription {
77    /// The icon's sprite sheet.
78    pub sprite_sheet: Option<String>,
79    /// The icon's sprite.
80    pub sprite: String,
81    /// The string table.
82    pub string_table: Option<String>,
83    /// The name's key.
84    pub name: String,
85    /// The key of the name in lower case, shown in the create bar.
86    pub name_lower: String,
87    /// The tooltip's key.
88    pub tooltip: String,
89    /// Message keys (`CreateOk`, `CreateBlocked`, …).
90    pub messages: Vec<String>,
91    // +0x64
92    /// Not identified yet.
93    pub category: i32,
94    // +0x60
95    /// Not identified yet.
96    pub value: i32,
97}
98
99/// Read a [`ToolbarDescription`] from the bytes of its resource.
100pub fn toolbar_description(data: &[u8]) -> Result<ToolbarDescription> {
101    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
102        if h.type_id != type_id("ToolbarDescription") {
103            return format_err(format!(
104                "expected ToolbarDescription, found {:#010x}",
105                h.type_id
106            ));
107        }
108        r.block(|r, _| {
109            Ok(ToolbarDescription {
110                sprite_sheet: r.external_ref()?,
111                sprite: r.string()?,
112                string_table: r.external_ref()?,
113                name: r.string()?,
114                name_lower: r.string()?,
115                tooltip: r.string()?,
116                messages: (0..r.count()?).map(|_| r.string()).collect::<Result<_>>()?,
117                category: r.i32()?,
118                value: r.i32()?,
119            })
120        })
121    })?;
122    Ok(value)
123}
124
125/// One entry of a `UIDescription` controller table.
126///
127/// Original: `FUN_140133530`.
128#[derive(Debug, Clone)]
129pub struct UiController {
130    /// CRC32 type id of the `UI::DialogController` to attach (0 = none, e.g. `StatusBarUI`
131    /// `0x509d1aea`).
132    pub type_id: u32,
133    /// The element to instantiate into the named slot.
134    pub element: Option<String>,
135    // +0x10
136    /// Controller configuration.
137    pub config: Option<String>,
138    // +0x18
139    /// Tab label element, used when the slot is a `TabElement`.
140    pub tab: Option<String>,
141    /// Name of the slot element in the dialog (`userGroup0`, `userTitle0`).
142    pub slot: String,
143}
144
145/// `UIDescription`: a utility window built from a host dialog with content placed into its named
146/// slots, each with an optional controller.
147///
148/// Original: `FUN_140133350`.
149#[derive(Debug, Clone)]
150pub struct UiDescription {
151    /// Host dialog, e.g. `Dialog\StandardDialog.rsc:resource`.
152    pub dialog: Option<String>,
153    // +0x40
154    /// Not identified yet.
155    pub flags: i32,
156    /// The controllers.
157    pub controllers: Vec<UiController>,
158    // +0x20
159    /// Not identified yet.
160    pub value: i32,
161}
162
163/// Read a [`UiDescription`] from the bytes of its resource.
164pub fn ui_description(data: &[u8]) -> Result<UiDescription> {
165    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
166        if h.type_id != type_id("UIDescription") {
167            return format_err(format!("expected UIDescription, found {:#010x}", h.type_id));
168        }
169        r.block(|r, _| {
170            let dialog = r.external_ref()?;
171            let flags = r.i32()?;
172            let mut controllers = Vec::new();
173            for _ in 0..r.count()? {
174                controllers.push(UiController {
175                    type_id: r.u32()?,
176                    element: r.external_ref()?,
177                    config: r.external_ref()?,
178                    tab: r.external_ref()?,
179                    slot: r.string()?,
180                });
181            }
182            Ok(UiDescription {
183                dialog,
184                flags,
185                controllers,
186                value: r.i32()?,
187            })
188        })
189    })?;
190    Ok(value)
191}
192
193// +0x20; +0x28; +0x30; +0x2c
194/// `StatusIconDescription`: the icon sprite sheet, icon size and screen-constant flag. The height
195/// offset is not serialized and stays 1.0.
196///
197/// Original: `FUN_140131650`, `FUN_140039280`.
198#[derive(Debug, Clone)]
199pub struct StatusIconDescription {
200    /// The icon sprite sheet.
201    pub sheet: Option<String>,
202    /// The icon size.
203    pub size: f32,
204    /// Whether the icon keeps its size on screen.
205    pub screen_constant: bool,
206}
207
208/// Read a [`StatusIconDescription`] from the bytes of its resource.
209pub fn status_icon_description(data: &[u8]) -> Result<StatusIconDescription> {
210    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
211        if h.type_id != type_id("StatusIconDescription") {
212            return format_err(format!(
213                "expected StatusIconDescription, found {:#010x}",
214                h.type_id
215            ));
216        }
217        r.block(|r, _| {
218            Ok(StatusIconDescription {
219                sheet: r.external_ref()?,
220                size: r.f32()?,
221                screen_constant: r.bool()?,
222            })
223        })
224    })?;
225    Ok(value)
226}