plasma_prp/age/
description.rs1use anyhow::{Context, Result};
2use std::path::Path;
3
4pub const PAGE_PREVENT_AUTO_LOAD: u8 = 0x01;
6pub const PAGE_LOAD_IF_SDL_PRESENT: u8 = 0x02;
7pub const PAGE_IS_LOCAL_ONLY: u8 = 0x04;
8pub const PAGE_IS_VOLATILE: u8 = 0x08;
9
10#[derive(Debug, Clone)]
12pub struct PageEntry {
13 pub name: String,
14 pub seq_suffix: u32,
15 pub flags: u8,
16}
17
18impl PageEntry {
19 pub fn auto_load(&self) -> bool {
21 self.flags & PAGE_PREVENT_AUTO_LOAD == 0
22 }
23}
24
25#[derive(Debug, Clone)]
27pub struct AgeDescription {
28 pub age_name: String,
29 pub start_date_time: u32,
30 pub day_length: f32,
31 pub max_capacity: i32,
32 pub linger_time: i32,
33 pub sequence_prefix: i32,
34 pub release_version: u32,
35 pub pages: Vec<PageEntry>,
36}
37
38impl AgeDescription {
39 pub fn from_file(path: &Path) -> Result<Self> {
41 let age_name = path.file_stem()
42 .and_then(|s| s.to_str())
43 .unwrap_or("unknown")
44 .to_string();
45
46 let content = std::fs::read_to_string(path)
47 .with_context(|| format!("Failed to read {}", path.display()))?;
48
49 Self::parse(&age_name, &content)
50 }
51
52 pub fn parse(age_name: &str, content: &str) -> Result<Self> {
54 let mut desc = Self {
55 age_name: age_name.to_string(),
56 start_date_time: 0,
57 day_length: 24.0,
58 max_capacity: -1,
59 linger_time: -1,
60 sequence_prefix: 0,
61 release_version: 0,
62 pages: Vec::new(),
63 };
64
65 let mut next_suffix = 1u32;
66
67 for line in content.lines() {
68 let line = line.trim();
69 if line.is_empty() || line.starts_with('#') || line.starts_with('[') {
70 continue;
71 }
72
73 let (key, value) = match line.split_once('=') {
74 Some(kv) => kv,
75 None => continue,
76 };
77
78 match key.trim() {
79 "StartDateTime" => {
80 desc.start_date_time = value.trim().parse().unwrap_or(0);
81 }
82 "DayLength" => {
83 desc.day_length = value.trim().parse().unwrap_or(24.0);
84 }
85 "MaxCapacity" => {
86 desc.max_capacity = value.trim().parse().unwrap_or(-1);
87 }
88 "LingerTime" => {
89 desc.linger_time = value.trim().parse().unwrap_or(-1);
90 }
91 "SequencePrefix" => {
92 desc.sequence_prefix = value.trim().parse().unwrap_or(0);
93 }
94 "ReleaseVersion" => {
95 desc.release_version = value.trim().parse().unwrap_or(0);
96 }
97 "Page" => {
98 let parts: Vec<&str> = value.trim().split(',').collect();
99 let name = parts[0].to_string();
100 let seq_suffix = if parts.len() > 1 {
101 parts[1].parse().unwrap_or(next_suffix)
102 } else {
103 next_suffix
104 };
105 let flags = if parts.len() > 2 {
106 parts[2].parse().unwrap_or(0)
107 } else {
108 0
109 };
110
111 next_suffix = seq_suffix + 1;
112
113 desc.pages.push(PageEntry { name, seq_suffix, flags });
114 }
115 _ => {}
116 }
117 }
118
119 Ok(desc)
120 }
121
122 pub fn auto_load_pages(&self) -> Vec<&PageEntry> {
124 self.pages.iter().filter(|p| p.auto_load()).collect()
125 }
126
127 pub fn page_location(&self, page: &PageEntry) -> u32 {
129 if self.sequence_prefix < 0 {
130 let combined = ((-self.sequence_prefix as u32) << 16) | (page.seq_suffix & 0xFFFF);
131 combined } else {
133 ((self.sequence_prefix as u32) << 16) | (page.seq_suffix & 0xFFFF)
134 }
135 }
136
137 pub fn prp_filename(&self, page: &PageEntry) -> String {
139 format!("{}_District_{}.prp", self.age_name, page.name)
140 }
141
142 pub fn common_page_filenames(&self) -> Vec<String> {
144 vec![
145 format!("{}_District_Textures.prp", self.age_name),
146 format!("{}_District_BuiltIn.prp", self.age_name),
147 ]
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_parse_cleft_age() {
157 let content = r#"StartDateTime=0000000000
158DayLength=24.000000
159MaxCapacity=1
160LingerTime=180
161SequencePrefix=7
162ReleaseVersion=0
163Page=BookRoom,2
164Page=Cleft,1
165Page=clftAtrusGoggles,30,1
166Page=Desert,0,1
167"#;
168 let desc = AgeDescription::parse("Cleft", content).unwrap();
169 assert_eq!(desc.age_name, "Cleft");
170 assert_eq!(desc.sequence_prefix, 7);
171 assert_eq!(desc.max_capacity, 1);
172 assert_eq!(desc.linger_time, 180);
173 assert_eq!(desc.pages.len(), 4);
174
175 assert!(desc.pages[0].auto_load());
177 assert_eq!(desc.pages[0].name, "BookRoom");
178 assert_eq!(desc.pages[0].seq_suffix, 2);
179
180 assert!(desc.pages[1].auto_load());
182
183 assert!(!desc.pages[2].auto_load());
185
186 let auto = desc.auto_load_pages();
188 assert_eq!(auto.len(), 2);
189
190 assert_eq!(desc.prp_filename(&desc.pages[0]), "Cleft_District_BookRoom.prp");
192
193 assert_eq!(desc.page_location(&desc.pages[0]), (7 << 16) | 2);
195 }
196}