-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
World File Parsing #320
Merged
+347
−6
Merged
World File Parsing #320
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
2b05f22
add world file parsing
JtotheThree 0eaed1e
add world file parsing
JtotheThree ed6cfa3
clarity and cleanup
JtotheThree b12187d
read world from reader
JtotheThree 8a57dcc
add option to preload maps in world
JtotheThree fdae0fe
fix documentation, revert auto load maps
JtotheThree 505d6ad
remove tmx map variable from worldmap
JtotheThree 427dcb8
Remove automatic dir pattern testing
JtotheThree 9695ab2
formatting and better docs
JtotheThree c87b118
formatting and better docs
JtotheThree 3c56e9d
Moved pattern utils to World impl
JtotheThree 9eab95d
Missed dep for world feature and cargo build step
JtotheThree b36eac9
match_path and match_paths
JtotheThree e72e805
add match_path to WorldPattern
JtotheThree 37f0ee6
reduce utf-8 checks on path iteration
JtotheThree 534ab0a
match_path_impl and readme update
JtotheThree 584b893
empty vecs instead of option
JtotheThree 7930290
empty vecs instead of option
JtotheThree 7f3b082
Oddities with readme
JtotheThree 82d9bfd
fix load_world docs
JtotheThree 091d074
fix source not populating
JtotheThree File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,158 @@ | ||
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, | ||
JtotheThree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// The [`WorldMap`]s defined by the world file. | ||
pub maps: Option<Vec<WorldMap>>, | ||
JtotheThree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Optional regex pattern to load maps. | ||
pub patterns: Option<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> { | ||
if let Some(patterns) = &self.patterns { | ||
for pattern in patterns { | ||
let captures = match pattern.regexp.captures(path.as_ref().to_str().unwrap()) { | ||
Some(captures) => captures, | ||
None => continue, | ||
}; | ||
|
||
let x = match captures.get(1) { | ||
Some(x) => x.as_str().parse::<i32>().unwrap(), | ||
None => continue, | ||
}; | ||
|
||
let y = match captures.get(2) { | ||
Some(y) => y.as_str().parse::<i32>().unwrap(), | ||
None => continue, | ||
}; | ||
|
||
// Calculate x and y positions based on the multiplier and offset. | ||
let x = x | ||
.checked_mul(pattern.multiplier_x) | ||
.ok_or(Error::RangeError( | ||
"Capture x * multiplierX causes overflow".to_string(), | ||
))? | ||
.checked_add(pattern.offset_x) | ||
.ok_or(Error::RangeError( | ||
"Capture x * multiplierX + offsetX causes overflow".to_string(), | ||
))?; | ||
|
||
let y = y | ||
.checked_mul(pattern.multiplier_y) | ||
.ok_or(Error::RangeError( | ||
"Capture y * multiplierY causes overflow".to_string(), | ||
))? | ||
.checked_add(pattern.offset_y) | ||
.ok_or(Error::RangeError( | ||
"Capture y * multiplierY + offsetY causes overflow".to_string(), | ||
))?; | ||
|
||
// Returning the first matched pattern aligns with how Tiled handles patterns. | ||
return Ok(WorldMap { | ||
filename: path.as_ref().to_str().unwrap().to_string(), | ||
x, | ||
y, | ||
width: None, | ||
height: None, | ||
}); | ||
} | ||
} | ||
|
||
Err(Error::NoMatchFound { | ||
path: path.as_ref().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() | ||
} | ||
} | ||
|
||
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 world: World = | ||
serde_json::from_str(&world_string).map_err(|err| Error::JsonDecodingError(err))?; | ||
|
||
Ok(world) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably also have a build check without all features. However this could be improved later on using
cargo hack
to check with the powerset of features instead. So all good here for now.