diff options
Diffstat (limited to 'src/world')
| -rw-r--r-- | src/world/component.rs | 23 | ||||
| -rw-r--r-- | src/world/icon.rs | 23 | ||||
| -rw-r--r-- | src/world/mod.rs | 113 | ||||
| -rw-r--r-- | src/world/room.rs | 52 | ||||
| -rw-r--r-- | src/world/wall.rs | 52 |
5 files changed, 263 insertions, 0 deletions
diff --git a/src/world/component.rs b/src/world/component.rs new file mode 100644 index 0000000..e8a8df9 --- /dev/null +++ b/src/world/component.rs @@ -0,0 +1,23 @@ +//! The world is made up of components. Every component must implement this +//! general trait that makes it possible to argue about certain characteristics +//! and may make it possible to transform some items. + +use crate::math::Rect; +use crate::transformable::NonRigidTransformable; + +/// Anything that can be added to the map or world must implement this trait. +pub trait Component { + /// Get the rectangle that contains the component in its entirety without excess. + fn bounding_rect(&self) -> Rect<f64>; + + /// If this component can be transformed in a non-rigid way, a dynamic + /// reference is returned, otherwise none. + fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> { + None + } + + /// The same as `as_non_rigid`, but mutably. + fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> { + None + } +} diff --git a/src/world/icon.rs b/src/world/icon.rs new file mode 100644 index 0000000..c8945fb --- /dev/null +++ b/src/world/icon.rs @@ -0,0 +1,23 @@ +//! Icons are world elements that have a specific size and cannot be stretched. They are usually used +//! as markers for specific places in the world. + +use super::Component; +use crate::math::{Rect, Vec2}; +use serde::{Deserialize, Serialize}; + +/// The icon datatype. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Icon { + /// The id of the icon is the icons position in the currently loaded icon_data vector. + pub id: usize, + /// The position of the icon on the map, given by the vector in meters. + pub position: Vec2<f64>, + /// Rotation of the icon texture in degrees. + pub rotation: f64, +} + +impl Component for Icon { + fn bounding_rect(&self) -> Rect<f64> { + Rect::new(self.position.x, self.position.y, 0., 0.) + } +} diff --git a/src/world/mod.rs b/src/world/mod.rs new file mode 100644 index 0000000..047ca5c --- /dev/null +++ b/src/world/mod.rs @@ -0,0 +1,113 @@ +//! The world contains all items of a session and the ways to manipulate them. +//! Generally speaking, manipulation will be done on the server side and then +//! the changes are sent to the client. + +pub mod component; +pub mod icon; +pub mod room; +pub mod wall; + +pub use component::*; +pub use icon::*; +pub use room::*; +pub use wall::*; + +use crate::stable_vec::StableVec; +use ron::de::from_reader; +use ron::ser::{to_string_pretty, PrettyConfig}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{self, Write}; +use std::path::Path; + +/// Main structure that contains all information about a world necessary to argue +/// about structures in it. It can be persistified and loaded from disk, as well +/// as sent over the network. Everything in it is assigned an id to make it easy +/// to argue about a specific item. IDs may only be assigned by the server, not +/// the client, since otherwise multiple clients may try to create the same item. +#[derive(Debug, Serialize, Deserialize)] +pub struct World { + rooms: StableVec<Room>, + walls: StableVec<Wall>, + icons: StableVec<Icon>, + used_ids: StableVec<()>, +} + +impl World { + /// Load the world data from a file. Fails if the file does not exist or + /// cannot be correctly parsed. + pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> { + let file = File::open(&path)?; + let data: Self = match from_reader(file) { + Ok(data) => data, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, err)); + } + }; + + Ok(data) + } + + /// Write the world data to the file located at `path`. If the file already + /// exists, it will be overwritten. If the write fails, an IO-Error is returned. + pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { + let mut file = File::create(&path)?; + + let pretty_conf = PrettyConfig::new() + .with_depth_limit(4) + .with_decimal_floats(true) + .with_separate_tuple_members(true) + .with_indentor("\t".to_owned()); + let string = match to_string_pretty(&self, pretty_conf) { + Ok(string) => string, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidInput, err)); + } + }; + + file.write_all(&string.as_bytes()) + } + + /// Add a room to the world. Currently, holes are not supported in the polygon, but this might + /// change later. + pub fn push_room(&mut self, room: Room) -> usize { + let id = self.used_ids.insert_anywhere(()); + self.rooms + .try_insert(id, room) + .expect("Id vecs out of sync"); + id + } + + /// Add a wall to the world. + pub fn push_wall(&mut self, wall: Wall) -> usize { + let id = self.used_ids.insert_anywhere(()); + self.walls + .try_insert(id, wall) + .expect("Id vecs out of sync"); + id + } + + /// Add an icon to the world. + pub fn push_icon(&mut self, icon: Icon) -> usize { + let id = self.used_ids.insert_anywhere(()); + self.icons + .try_insert(id, icon) + .expect("Id vecs out of sync"); + id + } + + /// Get all icons of the world. + pub fn icons(&self) -> &StableVec<Icon> { + &self.icons + } + + /// Get all rooms of the world. + pub fn rooms(&self) -> &StableVec<Room> { + &self.rooms + } + + /// Get all walls of the world. + pub fn walls(&self) -> &StableVec<Wall> { + &self.walls + } +} diff --git a/src/world/room.rs b/src/world/room.rs new file mode 100644 index 0000000..fed8890 --- /dev/null +++ b/src/world/room.rs @@ -0,0 +1,52 @@ +//! Polygon rooms are the standard rooms in graf karto. They can take the form of anything that a +//! [Polygon](crate::math::Polygon) can have. + +use super::Component; +use crate::math::{Polygon, Rect}; +use crate::transformable::NonRigidTransformable; +use nalgebra::{Matrix3, Point2}; +use serde::{Deserialize, Serialize}; + +/// A polygon shaped room, which can be placed and modified in the world. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Room { + shape: Polygon<f64>, +} + +impl Room { + /// Create a new room with the given shape. + pub fn new(shape: Polygon<f64>) -> Self { + Self { shape } + } + + /// Get the polygon this room is based on. + pub fn shape(&self) -> &Polygon<f64> { + &self.shape + } +} + +impl Component for Room { + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect_n(&self.shape.corners()) + } + + fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> { + Some(self as &dyn NonRigidTransformable) + } + + fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> { + Some(self as &mut dyn NonRigidTransformable) + } +} + +impl NonRigidTransformable for Room { + // XXX: This produces undefined behaviour when using a mirroring matrix, since + // the polygon corners must be sorted in counterclockwise direction. + fn apply_matrix(&mut self, matrix: &Matrix3<f64>) { + for corner in self.shape.corners_mut() { + *corner = matrix + .transform_point(&Point2::new(corner.x, corner.y)) + .into(); + } + } +} diff --git a/src/world/wall.rs b/src/world/wall.rs new file mode 100644 index 0000000..45b0b1e --- /dev/null +++ b/src/world/wall.rs @@ -0,0 +1,52 @@ +//! Walls, solid barriers that are generally unclimbable. +//! +//! This interpretation is generally up to the GM to decide, but generally +//! speaking, a wall cannot be crossed by a player. If special conditions apply +//! (for instance, when the player wants to scale the wall), a check is necessary. +//! If a check is not necessary, then maybe you were not thinking about a wall. + +use super::Component; +use crate::math::{LineSegment, Rect}; +use crate::transformable::NonRigidTransformable; +use nalgebra::Matrix3; +use serde::{Deserialize, Serialize}; + +/// Representation of a solid wall a player cannot go through, except if special +/// conditions apply. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Wall { + shape: LineSegment<f64>, +} + +impl Wall { + /// Create a new wall with the line segment defining it's start and end. + pub fn new(shape: LineSegment<f64>) -> Self { + Self { shape } + } + + /// Get the line shape this wall is based on. + pub fn shape(&self) -> &LineSegment<f64> { + &self.shape + } +} + +impl Component for Wall { + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect(self.shape.start, self.shape.end) + } + + fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> { + Some(self as &dyn NonRigidTransformable) + } + + fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> { + Some(self as &mut dyn NonRigidTransformable) + } +} + +impl NonRigidTransformable for Wall { + fn apply_matrix(&mut self, matrix: &Matrix3<f64>) { + self.shape.start = matrix.transform_point(&self.shape.start.into()).into(); + self.shape.end = matrix.transform_point(&self.shape.end.into()).into(); + } +} |
