-
Notifications
You must be signed in to change notification settings - Fork 106
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add world file parsing * add world file parsing * clarity and cleanup * read world from reader * add option to preload maps in world * fix documentation, revert auto load maps * remove tmx map variable from worldmap * Remove automatic dir pattern testing * formatting and better docs * formatting and better docs * Moved pattern utils to World impl * Missed dep for world feature and cargo build step * match_path and match_paths * add match_path to WorldPattern * reduce utf-8 checks on path iteration * match_path_impl and readme update * empty vecs instead of option * empty vecs instead of option * Oddities with readme * fix load_world docs * fix source not populating
- Loading branch information
1 parent
8f21563
commit c434e1b
Showing
11 changed files
with
347 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"maps": [ | ||
{ | ||
"fileName": "map01.tmx", | ||
"height": 640, | ||
"width": 960, | ||
"x": 0, | ||
"y": 0 | ||
}, | ||
{ | ||
"fileName": "map02.tmx", | ||
"height": 640, | ||
"width": 960, | ||
"x": 960, | ||
"y": 0 | ||
} | ||
], | ||
"onlyShowAdjacentMaps": false, | ||
"type": "world" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"patterns": [ | ||
{ | ||
"regexp": "map-x0*(\\d+)-y0*(\\d+)-.*\\.tmx", | ||
"multiplierX": 640, | ||
"multiplierY": 480, | ||
"offsetX": 240, | ||
"offsetY": -240 | ||
}, | ||
{ | ||
"regexp": "overworld-x0*(\\d+)-y0*(\\d+).tmx", | ||
"multiplierX": 640, | ||
"multiplierY": 480, | ||
"offsetX": 4192, | ||
"offsetY": 4192 | ||
}, | ||
{ | ||
"regexp": "OVERFLOW-x0*(\\d+)-y0*(\\d+).tmx", | ||
"multiplierX": 50000000, | ||
"multiplierY": 50000000, | ||
"offsetX": 4192, | ||
"offsetY": 4192 | ||
} | ||
], | ||
"type": "world" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
use std::{ | ||
io::Read, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use regex::Regex; | ||
use serde::Deserialize; | ||
|
||
use crate::{Error, ResourceReader}; | ||
|
||
/// A World is a list of maps files or regex patterns that define a layout of TMX maps. | ||
/// You can use the loader to further load the maps defined by the world. | ||
#[derive(Deserialize, PartialEq, Clone, Debug)] | ||
pub struct World { | ||
/// The path first used in a [`ResourceReader`] to load this world. | ||
#[serde(skip_deserializing)] | ||
pub source: PathBuf, | ||
/// The [`WorldMap`]s defined by the world file. | ||
#[serde(skip_serializing_if = "Vec::is_empty", default)] | ||
pub maps: Vec<WorldMap>, | ||
/// Optional regex pattern to load maps. | ||
#[serde(skip_serializing_if = "Vec::is_empty", default)] | ||
pub patterns: Vec<WorldPattern>, | ||
} | ||
|
||
impl World { | ||
/// Utility function to test a single path against all defined patterns. | ||
/// Returns a parsed [`WorldMap`] on the first matched pattern or an error if no patterns match. | ||
pub fn match_path(&self, path: impl AsRef<Path>) -> Result<WorldMap, Error> { | ||
let path_str = path.as_ref().to_str().expect("obtaining valid UTF-8 path"); | ||
|
||
for pattern in self.patterns.iter() { | ||
match pattern.match_path_impl(path_str) { | ||
Ok(world_map) => return Ok(world_map), | ||
// We ignore match errors here as the path may be matched by another pattern. | ||
Err(Error::NoMatchFound { .. }) => continue, | ||
Err(err) => return Err(err), | ||
} | ||
} | ||
|
||
Err(Error::NoMatchFound { | ||
path: path_str.to_owned(), | ||
}) | ||
} | ||
|
||
/// Utility function to test a vec of filenames against all defined patterns. | ||
/// Returns a vec of results with the parsed [`WorldMap`]s if it matches the pattern. | ||
pub fn match_paths<P: AsRef<Path>>(&self, paths: &[P]) -> Vec<Result<WorldMap, Error>> { | ||
paths | ||
.into_iter() | ||
.map(|path| self.match_path(path)) | ||
.collect() | ||
} | ||
} | ||
|
||
/// A WorldMap provides the information for a map in the world and its layout. | ||
#[derive(Deserialize, PartialEq, Clone, Debug)] | ||
pub struct WorldMap { | ||
/// The filename of the tmx map. | ||
#[serde(rename = "fileName")] | ||
pub filename: String, | ||
/// The x position of the map. | ||
pub x: i32, | ||
/// The y position of the map. | ||
pub y: i32, | ||
/// The optional width of the map. | ||
pub width: Option<i32>, | ||
/// The optional height of the map. | ||
pub height: Option<i32>, | ||
} | ||
|
||
/// A WorldPattern defines a regex pattern to automatically determine which maps to load and how to lay them out. | ||
#[derive(Deserialize, Clone, Debug)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct WorldPattern { | ||
/// The regex pattern to match against filenames. | ||
/// The first two capture groups should be the x integer and y integer positions. | ||
#[serde(with = "serde_regex")] | ||
pub regexp: Regex, | ||
/// The multiplier for the x position. | ||
pub multiplier_x: i32, | ||
/// The multiplier for the y position. | ||
pub multiplier_y: i32, | ||
/// The offset for the x position. | ||
pub offset_x: i32, | ||
/// The offset for the y position. | ||
pub offset_y: i32, | ||
} | ||
|
||
impl PartialEq for WorldPattern { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.multiplier_x == other.multiplier_x | ||
&& self.multiplier_y == other.multiplier_y | ||
&& self.offset_x == other.offset_x | ||
&& self.offset_y == other.offset_y | ||
&& self.regexp.to_string() == other.regexp.to_string() | ||
} | ||
} | ||
|
||
impl WorldPattern { | ||
/// Utility function to test a path against this pattern. | ||
/// Returns a parsed [`WorldMap`] on the first matched pattern or an error if no patterns match. | ||
pub fn match_path(&self, path: impl AsRef<Path>) -> Result<WorldMap, Error> { | ||
let path_str = path.as_ref().to_str().expect("obtaining valid UTF-8 path"); | ||
|
||
self.match_path_impl(path_str) | ||
} | ||
|
||
pub(crate) fn match_path_impl(&self, path: &str) -> Result<WorldMap, Error> { | ||
let captures = match self.regexp.captures(path) { | ||
Some(captures) => captures, | ||
None => { | ||
return Err(Error::NoMatchFound { | ||
path: path.to_owned(), | ||
}) | ||
} | ||
}; | ||
|
||
let x = match captures.get(1) { | ||
Some(x) => x.as_str().parse::<i32>().unwrap(), | ||
None => { | ||
return Err(Error::NoMatchFound { | ||
path: path.to_owned(), | ||
}) | ||
} | ||
}; | ||
|
||
let y = match captures.get(2) { | ||
Some(y) => y.as_str().parse::<i32>().unwrap(), | ||
None => { | ||
return Err(Error::NoMatchFound { | ||
path: path.to_owned(), | ||
}) | ||
} | ||
}; | ||
|
||
// Calculate x and y positions based on the multiplier and offset. | ||
let x = x | ||
.checked_mul(self.multiplier_x) | ||
.ok_or(Error::RangeError( | ||
"Capture x * multiplierX causes overflow".to_string(), | ||
))? | ||
.checked_add(self.offset_x) | ||
.ok_or(Error::RangeError( | ||
"Capture x * multiplierX + offsetX causes overflow".to_string(), | ||
))?; | ||
|
||
let y = y | ||
.checked_mul(self.multiplier_y) | ||
.ok_or(Error::RangeError( | ||
"Capture y * multiplierY causes overflow".to_string(), | ||
))? | ||
.checked_add(self.offset_y) | ||
.ok_or(Error::RangeError( | ||
"Capture y * multiplierY + offsetY causes overflow".to_string(), | ||
))?; | ||
|
||
Ok(WorldMap { | ||
filename: path.to_owned(), | ||
x, | ||
y, | ||
width: None, | ||
height: None, | ||
}) | ||
} | ||
} | ||
|
||
pub(crate) fn parse_world( | ||
world_path: &Path, | ||
reader: &mut impl ResourceReader, | ||
) -> Result<World, Error> { | ||
let mut path = reader | ||
.read_from(&world_path) | ||
.map_err(|err| Error::ResourceLoadingError { | ||
path: world_path.to_owned(), | ||
err: Box::new(err), | ||
})?; | ||
|
||
let mut world_string = String::new(); | ||
path.read_to_string(&mut world_string) | ||
.map_err(|err| Error::ResourceLoadingError { | ||
path: world_path.to_owned(), | ||
err: Box::new(err), | ||
})?; | ||
|
||
let mut world: World = | ||
serde_json::from_str(&world_string).map_err(|err| Error::JsonDecodingError(err))?; | ||
|
||
world.source = world_path.to_owned(); | ||
|
||
Ok(world) | ||
} |
Oops, something went wrong.