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}