aboutsummaryrefslogtreecommitdiff
path: root/src/world
diff options
context:
space:
mode:
authorArne Dußin2021-01-27 14:01:50 +0100
committerArne Dußin2021-02-02 22:16:15 +0100
commitf92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch)
tree20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/world
parentc60a6d07efb120724b308e29e8e70f27c87c952d (diff)
downloadgraf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.tar.gz
graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.zip
Rework graf karto to fit the client/server structure
Diffstat (limited to 'src/world')
-rw-r--r--src/world/component.rs23
-rw-r--r--src/world/icon.rs23
-rw-r--r--src/world/mod.rs113
-rw-r--r--src/world/room.rs52
-rw-r--r--src/world/wall.rs52
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();
+ }
+}