1#![deny(future_incompatible)]
7#![deny(nonstandard_style)]
8#![deny(rust_2018_idioms)]
9#![deny(unsafe_code)]
10#![warn(missing_docs)]
11#![warn(unused)]
12
13mod ai;
14mod bitmap;
15pub mod convert;
16mod format;
17mod header;
18mod map;
19mod player;
20mod triggers;
21mod types;
22mod victory;
23
24use format::SCXFormat;
25use genie_support::{ReadStringError, WriteStringError};
26use std::io::{self, Read, Write};
27
28pub use format::{ScenarioObject, TribeScen};
29pub use genie_support::{DecodeStringError, EncodeStringError};
30pub use genie_support::{StringKey, UnitTypeID};
31pub use header::{DLCOptions, SCXHeader};
32pub use map::{Map, Tile};
33pub use triggers::{Trigger, TriggerCondition, TriggerEffect, TriggerSystem};
34pub use types::*;
35pub use victory::{VictoryConditions, VictoryEntry, VictoryPointEntry};
36
37#[derive(Debug, thiserror::Error)]
40pub enum Error {
41 #[error("must have a file name")]
43 MissingFileNameError,
44 #[error("unsupported format version {:?}", .0)]
46 UnsupportedFormatVersionError(SCXVersion),
47 #[error("too many disabled techs: got {}, but requested version supports up to 20", .0)]
50 TooManyDisabledTechsError(i32),
51 #[error("requested version does not support disabling techs")]
54 CannotDisableTechsError,
55 #[error("requested version does not support disabling units")]
58 CannotDisableUnitsError,
59 #[error("too many disabled buildings: got {}, but requested version supports up to {}", .0, .1)]
62 TooManyDisabledBuildingsError(i32, i32),
63 #[error("requested version does not support disabling buildings")]
66 CannotDisableBuildingsError,
67 #[error(transparent)]
69 DecodeStringError(#[from] DecodeStringError),
70 #[error(transparent)]
72 EncodeStringError(#[from] EncodeStringError),
73 #[error(transparent)]
75 ParseDiplomaticStanceError(#[from] ParseDiplomaticStanceError),
76 #[error(transparent)]
78 ParseDataSetError(#[from] ParseDataSetError),
79 #[error(transparent)]
81 ParseDLCPackageError(#[from] ParseDLCPackageError),
82 #[error(transparent)]
84 ParseStartingAgeError(#[from] ParseStartingAgeError),
85 #[error(transparent)]
87 ParseAIErrorCodeError(#[from] num_enum::TryFromPrimitiveError<ai::AIErrorCode>),
88 #[error(transparent)]
90 IoError(#[from] io::Error),
91}
92
93impl From<ReadStringError> for Error {
94 fn from(err: ReadStringError) -> Error {
95 match err {
96 ReadStringError::IoError(err) => Error::IoError(err),
97 ReadStringError::DecodeStringError(err) => Error::DecodeStringError(err),
98 }
99 }
100}
101
102impl From<WriteStringError> for Error {
103 fn from(err: WriteStringError) -> Error {
104 match err {
105 WriteStringError::IoError(err) => Error::IoError(err),
106 WriteStringError::EncodeStringError(err) => Error::EncodeStringError(err),
107 }
108 }
109}
110
111pub type Result<T> = std::result::Result<T, Error>;
113
114#[derive(Debug, Clone)]
116pub struct Scenario {
117 format: SCXFormat,
118 version: VersionBundle,
119}
120
121impl Scenario {
122 pub fn read_from(input: impl Read) -> Result<Self> {
124 let format = SCXFormat::load_scenario(input)?;
125 let version = format.version();
126
127 Ok(Self { format, version })
128 }
129
130 #[deprecated = "Use Scenario::read_from instead."]
132 pub fn from<R: Read>(input: &mut R) -> Result<Self> {
133 Self::read_from(input)
134 }
135
136 pub fn write_to(&self, output: impl Write) -> Result<()> {
140 self.format.write_to(output, self.version())
141 }
142
143 pub fn write_to_version(&self, output: impl Write, version: &VersionBundle) -> Result<()> {
145 self.format.write_to(output, version)
146 }
147
148 #[inline]
150 pub fn format_version(&self) -> SCXVersion {
151 self.version().format
152 }
153
154 #[inline]
156 pub fn header_version(&self) -> u32 {
157 self.version().header
158 }
159
160 #[inline]
162 pub fn data_version(&self) -> f32 {
163 self.version().data
164 }
165
166 #[inline]
168 pub fn header(&self) -> &SCXHeader {
169 &self.format.header
170 }
171
172 #[inline]
174 pub fn description(&self) -> Option<&str> {
175 self.format.tribe_scen.description()
176 }
177
178 #[inline]
180 pub fn filename(&self) -> &str {
181 &self.format.tribe_scen.base.name
182 }
183
184 #[inline]
186 pub fn version(&self) -> &VersionBundle {
187 &self.version
188 }
189
190 #[inline]
192 pub fn requires_dlc(&self, dlc: DLCPackage) -> bool {
193 match &self.header().dlc_options {
194 Some(options) => options.dependencies.iter().any(|dep| *dep == dlc),
195 None => false,
196 }
197 }
198
199 #[inline]
203 pub fn mod_name(&self) -> Option<&str> {
204 self.format.mod_name()
205 }
206
207 #[inline]
209 pub fn objects(&self) -> impl Iterator<Item = &ScenarioObject> {
210 self.format
211 .player_objects
212 .iter()
213 .map(|list| list.iter())
214 .flatten()
215 }
216
217 #[inline]
219 pub fn objects_mut(&mut self) -> impl Iterator<Item = &mut ScenarioObject> {
220 self.format
221 .player_objects
222 .iter_mut()
223 .map(|list| list.iter_mut())
224 .flatten()
225 }
226
227 #[inline]
229 pub fn map(&self) -> &Map {
230 &self.format.map
231 }
232
233 #[inline]
235 pub fn map_mut(&mut self) -> &mut Map {
236 &mut self.format.map
237 }
238
239 #[inline]
241 pub fn triggers(&self) -> Option<&TriggerSystem> {
242 self.format.triggers.as_ref()
243 }
244
245 #[inline]
247 pub fn triggers_mut(&mut self) -> Option<&mut TriggerSystem> {
248 self.format.triggers.as_mut()
249 }
250}